PHP 5.6 introduced a cool feature called argument unpacking: the splat operator
unpacks arrays or objects implementing Traversable
interfaces into argument lists. Its most evident application is to call functions without having to resort to call_user_func_array()
.
For example, in pre-5.6 the only (sane) way was to use call_user_func_array()
like this:
function call_func(array $args) { $func = 'other_func'; call_user_func_array($func, $args); }
Since PHP 5.6 it is possible to do it this way:
function call_func(array $args) { $other_func(...$args); }
New features are cool, and I wanted to benchmark it for a long time (how old is PHP 5.6?), but never had free time for that. Until today.
I was looking at Slim’s DeferredCallable::__invoke() implementation and wondered if it would benefit from a dynamic call:
public function __invoke() { $callable = $this->resolveCallable($this->callable); if ($callable instanceof Closure) { $callable = $callable->bindTo($this->container); } $args = func_get_args(); return call_user_func_array($callable, $args); }
So I decided to write a quick benchmark using PHPBench:
function f($a, $b, $c) {} /** * @Revs(1000000) * @Iterations(5) * @Warmup(2) * @OutputMode("throughput") * @OutputTimeUnit("seconds", precision=1) */ class FunctionCallBench { public function benchCallUserFuncArray() { $args = [1, 2, 4]; $func = 'f'; call_user_func_array($func, $args); } public function benchDyncmicCall() { $args = [1, 2, 4]; $func = 'f'; $func(...$args); } }
Benchmark parameters: 5 iterations, 1,000,000 revolutions, 2 warmup revolutions (please read the documentation for an explanation what this means).
I ran the benchmark against PHP 7.0, 7.1, and 7.2, both thread safe (ZTS) and non-thread safe (NTS) builds (what is this?).
These are the results of the benchmark (the table shows mode values).
call_user_func_array Megaops/s |
Dynamic Call Megaops/s |
|
---|---|---|
PHP 7.0.30 NTS | 10.66 | 11.34 |
PHP 7.0.30 ZTS | 7.38 | 7.62 |
PHP 7.1.17 NTS | 16.71 | 17.05 |
PHP 7.1.17 ZTS | 11.42 | 11.62 |
PHP 7.2.5 NTS | 17.23 | 21.46 |
PHP 7.2.5 ZTS | 12.21 | 12.12 |
Conclusions:
- for ZTS builds, performance of both methods is comparable;
- for NTS builds, dynamic calls are faster than
call_user_func_array()
; - NTS is faster than ZTS;
- 7.2 is faster than 7.1, which is faster than 7.0 (at least in this particular case).