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).