PHP 5.6 introduced a cool feature called argument unpacking: the “splat operator” unpacks arrays or objects that implement the Traversable interface into argument lists. Its most obvious application is to call functions without resorting 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 have wanted to benchmark them for a long time (how old is PHP 5.6?), but I never had the free time 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, two warmup revolutions (please read the documentation for an explanation of 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 benchmark results (the table shows the mode values).

PHP Versioncall_user_func_array
Megaops/s
Dynamic Call
Megaops/s
7.0.30 NTS10.6611.34
7.0.30 ZTS7.387.62
7.1.17 NTS16.7117.05
7.1.17 ZTS11.4211.62
7.2.5 NTS17.2321.46
7.2.5 ZTS12.2112.12

Conclusions:

  • For ZTS builds, the 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 *