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

Performance of call_user_func_array() vs dynamic call
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).
PHP: call_user_func_array vs Dynamic Call
Tagged on:     

Leave a Reply

Your email address will not be published. Required fields are marked *