zend_compile: Optimize array_map() with callable convert callback into foreach#20934
zend_compile: Optimize array_map() with callable convert callback into foreach#20934TimWolla merged 10 commits intophp:masterfrom
array_map() with callable convert callback into foreach#20934Conversation
|
I like this! I assume something similar is also possible for Closures passed directly as arg, like |
|
On that note, I wonder whether it would make sense to expose this as API, add a function pointer on zend_internal_function, and whenever a function is encountered during compilation and it has this function pointer, it's called with the ast of its arguments and can emit opcodes by itself (or just return false and normal compilation happens). Rather than centralizing this in compiler (the file is big enough :-P, and it would make it extensible; extensions could play around with this too). |
It probably would for all the reasons that you mentioned. |
55be63c to
fb31333
Compare
I assume getting scoping right is getting complicated quickly. Even preserving the Closure and compiling it as: $c = fn ($x) => $x + 1;
foreach ($array as $key => $val) $result[$key] = $c($val);is not immediately obvious to me that it is safe (e.g. with regard to variable capturing and scoping). I've opted to support only CALLABLE_CONVERT for now, since those should already be pretty useful once PFA lands and they don't come with the concerns above. |
Unclear if this did something for performance, but done. |
b257e63 to
602f215
Compare
|
Will this also work for ? |
No(t with this PR). This is what Bob mentioned in his first comment below the line. |
|
CI is green 🥳 |
| zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); | ||
| zend_ast *call_args = zend_ast_create_list(0, ZEND_AST_ARG_LIST); | ||
| for (uint32_t i = 0; i < callback_args->children; i++) { | ||
| zend_ast *child = callback_args->child[i]; | ||
| if (child->kind == ZEND_AST_PLACEHOLDER_ARG) { | ||
| call_args = zend_ast_list_add(call_args, zend_ast_create_znode(&value)); | ||
| } else { | ||
| ZEND_ASSERT(0 && "not implemented"); | ||
| call_args = zend_ast_list_add(call_args, child); | ||
| } | ||
| } |
There was a problem hiding this comment.
For PFAs, each argument can be either of:
- kind == ZEND_AST_PLACEHOLDER_ARG && attr == ZEND_PLACEHOLDER_VARIADIC: This is a
... - Else if kind == ZEND_AST_PLACEHOLDER_ARG: This is a
? - Else: This is a pre-bound arg (a normal arg)
I think that you could reuse zend_partial_apply() from https://github.com/php/php-src/pull/20848/changes#diff-85701127596aca0e597bd7961b5d59cdde4f6bb3e2a109a22be859ab7568b4d2R6704 to build call_args from callback_args.
There was a problem hiding this comment.
Yes, my plan was to either adjust this PR if PFA lands first or to adjust the PFA PR if that one lands first, since it is currently unreachable except for ....
I hope to follow up with array_filter() and array_find(), which might require multiple arguments to fill in. Perhaps the zend_partial_apply() could be made variadic to accept any number of values that are then filled into the correct positions if more than one placeholder is specified?
…nto 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
7a95569 to
2068a9d
Compare
Following php#20934 which introduced the OPcode.
Following #20934 which introduced the OPcode.
For:
This is ~1.1× faster:
With JIT it becomes ~1.7× faster: