Skip to content

Commit fb31333

Browse files
committed
zend_compile: Optimize array_map() with callable convert callback into foreach
For: <?php function plus1($x) { return $x + 1; } $array = array_fill(0, 100, 1); $count = 0; for ($i = 0; $i < 100_000; $i++) { $count += count(array_map(plus1(...), $array)); } var_dump($count); This is ~1.1× faster: Benchmark 1: /tmp/test/before -d opcache.enable_cli=1 /tmp/test/test6.php Time (mean ± σ): 172.2 ms ± 0.5 ms [User: 167.8 ms, System: 4.2 ms] Range (min … max): 171.6 ms … 173.1 ms 17 runs Benchmark 2: /tmp/test/after -d opcache.enable_cli=1 /tmp/test/test6.php Time (mean ± σ): 155.1 ms ± 1.3 ms [User: 150.6 ms, System: 4.2 ms] Range (min … max): 154.2 ms … 159.3 ms 18 runs Summary /tmp/test/after -d opcache.enable_cli=1 /tmp/test/test6.php ran 1.11 ± 0.01 times faster than /tmp/test/before -d opcache.enable_cli=1 /tmp/test/test6.php With JIT it becomes ~1.7× faster: Benchmark 1: /tmp/test/before -d opcache.enable_cli=1 -d opcache.jit=tracing /tmp/test/test6.php Time (mean ± σ): 166.9 ms ± 0.6 ms [User: 162.7 ms, System: 4.1 ms] Range (min … max): 166.1 ms … 167.9 ms 17 runs Benchmark 2: /tmp/test/after -d opcache.enable_cli=1 -d opcache.jit=tracing /tmp/test/test6.php Time (mean ± σ): 94.5 ms ± 2.7 ms [User: 90.4 ms, System: 3.9 ms] Range (min … max): 92.5 ms … 103.1 ms 31 runs Summary /tmp/test/after -d opcache.enable_cli=1 -d opcache.jit=tracing /tmp/test/test6.php ran 1.77 ± 0.05 times faster than /tmp/test/before -d opcache.enable_cli=1 -d opcache.jit=tracing /tmp/test/test6.php
1 parent 175b7bc commit fb31333

File tree

8 files changed

+899
-567
lines changed

8 files changed

+899
-567
lines changed

Zend/zend_compile.c

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5027,7 +5027,92 @@ static zend_result zend_compile_func_clone(znode *result, const zend_ast_list *a
50275027
return SUCCESS;
50285028
}
50295029

5030-
static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *lcname, zend_ast_list *args, uint32_t type) /* {{{ */
5030+
static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *args, zend_string *lcname, uint32_t lineno) /* {{{ */
5031+
{
5032+
/* Bail out if we do not have exactly two parameters. */
5033+
if (args->children != 2) {
5034+
return FAILURE;
5035+
}
5036+
5037+
zend_ast *callback = args->child[0];
5038+
zend_eval_const_expr(&callback);
5039+
5040+
/* Bail out if the callback is not a FCC/PFA. */
5041+
if (callback->kind != ZEND_AST_CALL) {
5042+
return FAILURE;
5043+
}
5044+
zend_ast *args_ast = zend_ast_call_get_args(callback);
5045+
if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) {
5046+
return FAILURE;
5047+
}
5048+
5049+
znode value;
5050+
value.op_type = IS_TMP_VAR;
5051+
value.u.op.var = get_temporary_variable();
5052+
5053+
zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args);
5054+
zend_ast *call_args = zend_ast_create_list(0, ZEND_AST_ARG_LIST);
5055+
for (uint32_t i = 0; i < callback_args->children; i++) {
5056+
zend_ast *child = callback_args->child[i];
5057+
if (child->kind == ZEND_AST_PLACEHOLDER_ARG) {
5058+
call_args = zend_ast_list_add(call_args, zend_ast_create_znode(&value));
5059+
} else {
5060+
ZEND_ASSERT(0 && "not implemented");
5061+
call_args = zend_ast_list_add(call_args, child);
5062+
}
5063+
}
5064+
5065+
zend_op *opline;
5066+
5067+
znode array;
5068+
zend_compile_expr(&array, args->child[1]);
5069+
5070+
/* Verify that the input array actually is an array. */
5071+
znode name;
5072+
name.op_type = IS_CONST;
5073+
ZVAL_STR_COPY(&name.u.constant, lcname);
5074+
opline = zend_emit_op(NULL, ZEND_TYPE_ASSERT, &name, &array);
5075+
opline->lineno = lineno;
5076+
opline->extended_value = 2;
5077+
const zval *fbc_zv = zend_hash_find(CG(function_table), lcname);
5078+
const Bucket *fbc_bucket = (const Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val));
5079+
Z_EXTRA_P(CT_CONSTANT(opline->op1)) = fbc_bucket - CG(function_table)->arData;
5080+
5081+
/* Initialize the result array. */
5082+
zend_emit_op(result, ZEND_INIT_ARRAY, NULL, NULL);
5083+
5084+
/* foreach loop starts here. */
5085+
znode key;
5086+
5087+
uint32_t opnum_reset = get_next_op_number();
5088+
znode reset_node;
5089+
zend_emit_op(&reset_node, ZEND_FE_RESET_R, &array, NULL);
5090+
zend_begin_loop(ZEND_FE_FREE, &reset_node, false);
5091+
uint32_t opnum_fetch = get_next_op_number();
5092+
zend_emit_op_tmp(&key, ZEND_FE_FETCH_R, &reset_node, &value);
5093+
5094+
/* loop body */
5095+
znode call_result;
5096+
zend_compile_expr(&call_result, zend_ast_create(ZEND_AST_CALL, callback->child[0], call_args));
5097+
opline = zend_emit_op(NULL, ZEND_ADD_ARRAY_ELEMENT, &call_result, &key);
5098+
SET_NODE(opline->result, result);
5099+
/* end loop body */
5100+
5101+
zend_emit_jump(opnum_fetch);
5102+
5103+
uint32_t opnum_loop_end = get_next_op_number();
5104+
opline = &CG(active_op_array)->opcodes[opnum_reset];
5105+
opline->op2.opline_num = opnum_loop_end;
5106+
opline = &CG(active_op_array)->opcodes[opnum_fetch];
5107+
opline->extended_value = opnum_loop_end;
5108+
5109+
zend_end_loop(opnum_fetch, &reset_node);
5110+
zend_emit_op(NULL, ZEND_FE_FREE, &reset_node, NULL);
5111+
5112+
return SUCCESS;
5113+
}
5114+
5115+
static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *lcname, zend_ast_list *args, uint32_t type, uint32_t lineno) /* {{{ */
50315116
{
50325117
if (zend_string_equals_literal(lcname, "strlen")) {
50335118
return zend_compile_func_strlen(result, args);
@@ -5099,12 +5184,14 @@ static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *
50995184
return zend_compile_func_printf(result, args);
51005185
} else if (zend_string_equals(lcname, ZSTR_KNOWN(ZEND_STR_CLONE))) {
51015186
return zend_compile_func_clone(result, args);
5187+
} else if (zend_string_equals_literal(lcname, "array_map")) {
5188+
return zend_compile_func_array_map(result, args, lcname, lineno);
51025189
} else {
51035190
return FAILURE;
51045191
}
51055192
}
51065193

5107-
static zend_result zend_try_compile_special_func(znode *result, zend_string *lcname, zend_ast_list *args, const zend_function *fbc, uint32_t type) /* {{{ */
5194+
static zend_result zend_try_compile_special_func(znode *result, zend_string *lcname, zend_ast_list *args, const zend_function *fbc, uint32_t type, uint32_t lineno) /* {{{ */
51085195
{
51095196
if (CG(compiler_options) & ZEND_COMPILE_NO_BUILTINS) {
51105197
return FAILURE;
@@ -5120,7 +5207,7 @@ static zend_result zend_try_compile_special_func(znode *result, zend_string *lcn
51205207
return FAILURE;
51215208
}
51225209

5123-
if (zend_try_compile_special_func_ex(result, lcname, args, type) == SUCCESS) {
5210+
if (zend_try_compile_special_func_ex(result, lcname, args, type, lineno) == SUCCESS) {
51245211
return SUCCESS;
51255212
}
51265213

@@ -5263,7 +5350,7 @@ static void zend_compile_call(znode *result, const zend_ast *ast, uint32_t type)
52635350

52645351
if (!is_callable_convert &&
52655352
zend_try_compile_special_func(result, lcname,
5266-
zend_ast_get_list(args_ast), fbc, type) == SUCCESS
5353+
zend_ast_get_list(args_ast), fbc, type, ast->lineno) == SUCCESS
52675354
) {
52685355
zend_string_release_ex(lcname, 0);
52695356
zval_ptr_dtor(&name_node.u.constant);

Zend/zend_vm_def.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8858,6 +8858,32 @@ ZEND_VM_C_LABEL(type_check_resource):
88588858
}
88598859
}
88608860

8861+
ZEND_VM_HOT_HANDLER(211, ZEND_TYPE_ASSERT, CONST, ANY, NUM)
8862+
{
8863+
USE_OPLINE
8864+
SAVE_OPLINE();
8865+
8866+
zval *value = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
8867+
8868+
zend_function *fbc;
8869+
{
8870+
zval *fname = (zval*)RT_CONSTANT(opline, opline->op1);
8871+
ZEND_ASSERT(Z_EXTRA_P(fname) != 0);
8872+
fbc = Z_FUNC(EG(function_table)->arData[Z_EXTRA_P(fname)].val);
8873+
ZEND_ASSERT(fbc->type != ZEND_USER_FUNCTION);
8874+
}
8875+
8876+
zend_arg_info *arginfo = &fbc->common.arg_info[opline->extended_value - 1];
8877+
if (UNEXPECTED(!ZEND_TYPE_CONTAINS_CODE(arginfo->type, Z_TYPE_P(value)))) {
8878+
const char *param_name = get_function_arg_name(fbc, opline->extended_value);
8879+
zend_string *expected = zend_type_to_string(arginfo->type);
8880+
zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), opline->extended_value, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value));
8881+
zend_string_release(expected);
8882+
}
8883+
8884+
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
8885+
}
8886+
88618887
ZEND_VM_HOT_HANDLER(122, ZEND_DEFINED, CONST, ANY, CACHE_SLOT)
88628888
{
88638889
USE_OPLINE

0 commit comments

Comments
 (0)