Skip to content

Commit 8e216a2

Browse files
committed
zend_execute: Improve type checking performance when passing a Closure to a callable parameter
With `Closure`s existing since 5.3 and first class callables since 8.1, nowadays the most common type of callable likely is a `Closure`. Type checking a `Closure` argument for a `callable` parameter however does quite a bit of work, when a simple CE check would suffice. We thus do this. For: <?php function closure_(\Closure $f) { $f('abc'); } function callable_(callable $f) { $f('abc'); } $c = strrev(...); for ($i = 0; $i < 10000000; $i++) { closure_($c); } with the call in the loop appropriately replaced and running on a Intel(R) Core(TM) i7-1365U, I get the following timings: Benchmark 1: /tmp/bench/before callable.php Time (mean ± σ): 368.9 ms ± 5.2 ms [User: 365.0 ms, System: 3.6 ms] Range (min … max): 359.8 ms … 374.5 ms 10 runs Benchmark 2: /tmp/bench/before closure.php Time (mean ± σ): 283.3 ms ± 6.0 ms [User: 279.6 ms, System: 3.4 ms] Range (min … max): 274.1 ms … 293.2 ms 10 runs Benchmark 3: /tmp/bench/after callable.php Time (mean ± σ): 279.9 ms ± 10.1 ms [User: 276.3 ms, System: 3.4 ms] Range (min … max): 269.6 ms … 301.5 ms 10 runs Benchmark 4: /tmp/bench/after closure.php Time (mean ± σ): 283.4 ms ± 2.3 ms [User: 279.5 ms, System: 3.6 ms] Range (min … max): 279.7 ms … 286.6 ms 10 runs Summary /tmp/bench/after callable.php ran 1.01 ± 0.04 times faster than /tmp/bench/before closure.php 1.01 ± 0.04 times faster than /tmp/bench/after closure.php 1.32 ± 0.05 times faster than /tmp/bench/before callable.php The “standard” `array_find()` micro-benchmark of: <?php $array = range(1, 10000); $result = 0; for ($i = 0; $i < 5000; $i++) { $result += array_find($array, static function ($item) { return $item === 5000; }); } var_dump($result); Results in: $ hyperfine -L version before,after '/tmp/bench/{version} native.php' Benchmark 1: /tmp/bench/before native.php Time (mean ± σ): 637.3 ms ± 6.4 ms [User: 632.4 ms, System: 3.9 ms] Range (min … max): 626.8 ms … 644.9 ms 10 runs Benchmark 2: /tmp/bench/after native.php Time (mean ± σ): 572.0 ms ± 3.8 ms [User: 567.8 ms, System: 3.8 ms] Range (min … max): 567.0 ms … 580.1 ms 10 runs Summary /tmp/bench/after native.php ran 1.11 ± 0.01 times faster than /tmp/bench/before native.php see php#18157 (comment)
1 parent a32f491 commit 8e216a2

File tree

1 file changed

+8
-2
lines changed

1 file changed

+8
-2
lines changed

Zend/zend_execute.c

+8-2
Original file line numberDiff line numberDiff line change
@@ -1177,8 +1177,14 @@ static zend_always_inline bool zend_check_type_slow(
11771177
}
11781178

11791179
const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type);
1180-
if ((type_mask & MAY_BE_CALLABLE) &&
1181-
zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) {
1180+
if (
1181+
(type_mask & MAY_BE_CALLABLE)
1182+
&& (
1183+
/* Fast check for closures. */
1184+
EXPECTED(Z_OBJCE_P(arg) == zend_ce_closure)
1185+
|| zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)
1186+
)
1187+
) {
11821188
return 1;
11831189
}
11841190
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {

0 commit comments

Comments
 (0)