使用显式参数调用函数与call_user_func_array()

时间:2011-12-01 15:00:42

标签: php

本周早些时候我看到了一段代码(不幸的是,我无法检索),我对作者实施__call()魔术方法的方式感到好奇。代码看起来如下所示:

class Sample
{
    protected function test()
    {
        var_dump(func_get_args());
    }
    public function __call($func, $args)
    {
        if(!method_exists($this, $func))
        {
            return null;
        }
        switch(count($args))
        {
            case 0:
                return $this->$func();
            case 1:
                return $this->$func($args[0]);
            case 2:
                return $this->$func($args[0], $args[1]);
            case 3:
                return $this->$func($args[0], $args[1], $args[2]);
            case 4:
                return $this->$func($args[0], $args[1], $args[2], $args[3]);
            case 5:
                return $this->$func($args[0], $args[1], $args[2], $args[3], $args[4]);
            default:
                return call_user_func_array($this->$func, $args);
        }
    }
}
    
$obj = new Sample();
$obj->test("Hello World"); // Would be called via switch label 1

正如您所看到的,作者可能刚刚使用call_user_func_array()并完全放弃了开关,这样我就会相信(希望)有一些明智的推理背后。

我能想到的唯一原因可能是对call_user_func_array()的函数调用的一些开销,但这似乎不足以成为使用一堆case语句的理由。这里有一个角度我似乎没有得到吗?

2 个答案:

答案 0 :(得分:19)

原因是call_user_func_array上有开销。它有额外的函数调用的开销。通常这是在微秒范围内,但在两种情况下它可能变得很重要:

  1. 递归函数调用

    由于它正在向堆栈添加另一个调用,因此它将使堆栈使用量增加一倍。因此,您可能遇到问题(使用xdebug或内存约束),如果您的堆栈用完,将导致应用程序崩溃。在应用程序(或部分)中,使用此样式方法可以将堆栈使用量降低多达33%(这可能是应用程序运行和崩溃之间的差异)

  2. 性能

    如果你经常调用这个函数,那么那些微秒可以显着增加。由于这是在一个框架中(看起来像Lithium完成的事情),它可能在应用程序的生命周期中被称为数十,数百甚至数千次。因此,即使每个单独的呼叫都是微优化,效果也会显着增加。

  3. 是的,您可以删除交换机并将其替换为call_user_func_array,并且在功能方面它将是100%相同的。但是你将失去上面提到的两个优化优势。

    编辑并证明性能差异:

    我决定自己做一个基准测试。这是我使用的确切来源的链接:

    http://codepad.viper-7.com/s32CSb(也包含在此答案的底部供参考)

    现在,我在Linux系统,Windows系统和键盘的站点上测试了它(2个命令行,1个在线,1个启用了XDebug)所有运行5.3.6或5.3.8

    结论

    由于结果很长,我将首先总结一下。

    如果你打电话给这个,那么这样做的微优化。当然,个人通话是微不足道的。但如果它会被大量使用,它可以节省相当多的时间。

    现在,值得注意的是,除了其中一个测试之外的所有测试都是使用XDebug 关闭运行的。这非常重要,因为xdebug似乎会显着改变基准测试的结果。

    以下是原始结果:

    的Linux

    With 0 Arguments:
    test1 in 0.0898239612579 Seconds
    test2 in 0.0540208816528 Seconds
    testObj1 in 0.118539094925 Seconds
    testObj2 in 0.0492739677429 Seconds
    
    With 1 Arguments:
    test1 in 0.0997269153595 Seconds
    test2 in 0.053689956665 Seconds
    testObj1 in 0.137704849243 Seconds
    testObj2 in 0.0436580181122 Seconds
    
    With 2 Arguments:
    test1 in 0.0883569717407 Seconds
    test2 in 0.0551269054413 Seconds
    testObj1 in 0.115921974182 Seconds
    testObj2 in 0.0550417900085 Seconds
    
    With 3 Arguments:
    test1 in 0.0809321403503 Seconds
    test2 in 0.0630970001221 Seconds
    testObj1 in 0.124716043472 Seconds
    testObj2 in 0.0640230178833 Seconds
    
    With 4 Arguments:
    test1 in 0.0859131813049 Seconds
    test2 in 0.0723040103912 Seconds
    testObj1 in 0.137611865997 Seconds
    testObj2 in 0.0707349777222 Seconds
    
    With 5 Arguments:
    test1 in 0.109707832336 Seconds
    test2 in 0.122457027435 Seconds
    testObj1 in 0.201376914978 Seconds
    testObj2 in 0.217674016953 Seconds
    

    (我实际上跑了十几次,结果是一致的)。因此,您可以清楚地看到,在该系统上,将开关用于具有3个或更少参数的函数要快得多。对于4个参数,它足够接近有资格作为微优化。对于5它更慢(由于switch语句的开销)。

    现在,对象是另一个故事。对于对象,即使使用4个参数,使用switch语句也会显着更快。而且5个论点稍慢。

    With 0 Arguments:
    test1 in 0.078088998794556 Seconds
    test2 in 0.040416955947876 Seconds
    testObj1 in 0.092448949813843 Seconds
    testObj2 in 0.044382095336914 Seconds
    
    With 1 Arguments:
    test1 in 0.084033012390137 Seconds
    test2 in 0.049020051956177 Seconds
    testObj1 in 0.098193168640137 Seconds
    testObj2 in 0.055608987808228 Seconds
    
    With 2 Arguments:
    test1 in 0.092596054077148 Seconds
    test2 in 0.059282064437866 Seconds
    testObj1 in 0.10753011703491 Seconds
    testObj2 in 0.06486701965332 Seconds
    
    With 3 Arguments:
    test1 in 0.10003399848938 Seconds
    test2 in 0.073707103729248 Seconds
    testObj1 in 0.11481595039368 Seconds
    testObj2 in 0.072822093963623 Seconds
    
    With 4 Arguments:
    test1 in 0.10518193244934 Seconds
    test2 in 0.076627969741821 Seconds
    testObj1 in 0.1221661567688 Seconds
    testObj2 in 0.080114841461182 Seconds
    
    With 5 Arguments:
    test1 in 0.11016392707825 Seconds
    test2 in 0.14898705482483 Seconds
    testObj1 in 0.13080286979675 Seconds
    testObj2 in 0.15970706939697 Seconds
    

    同样,就像Linux一样,除了5个参数(预期)之外,每个案例都会更快。所以没有什么不正常的。

    键盘

    With 0 Arguments:
    test1 in 0.094165086746216 Seconds
    test2 in 0.046183824539185 Seconds
    testObj1 in 0.088129043579102 Seconds
    testObj2 in 0.046132802963257 Seconds
    
    With 1 Arguments:
    test1 in 0.093621969223022 Seconds
    test2 in 0.054486036300659 Seconds
    testObj1 in 0.11912703514099 Seconds
    testObj2 in 0.053775072097778 Seconds
    
    With 2 Arguments:
    test1 in 0.099776029586792 Seconds
    test2 in 0.072152853012085 Seconds
    testObj1 in 0.10576200485229 Seconds
    testObj2 in 0.065294027328491 Seconds
    
    With 3 Arguments:
    test1 in 0.11053204536438 Seconds
    test2 in 0.088426113128662 Seconds
    testObj1 in 0.11045718193054 Seconds
    testObj2 in 0.073081970214844 Seconds
    
    With 4 Arguments:
    test1 in 0.11662006378174 Seconds
    test2 in 0.085783958435059 Seconds
    testObj1 in 0.11683893203735 Seconds
    testObj2 in 0.081549882888794 Seconds
    
    With 5 Arguments:
    test1 in 0.12763905525208 Seconds
    test2 in 0.15642619132996 Seconds
    testObj1 in 0.12538290023804 Seconds
    testObj2 in 0.16010403633118 Seconds
    

    这显示与Linux相同的图片。使用4个或更少的参数,通过交换机运行它会明显加快。使用5个参数,切换时速度明显变慢。

    使用XDebug的Windows

    With 0 Arguments:
    test1 in 0.31674790382385 Seconds
    test2 in 0.31161189079285 Seconds
    testObj1 in 0.40747404098511 Seconds
    testObj2 in 0.32526516914368 Seconds
    
    With 1 Arguments:
    test1 in 0.32827591896057 Seconds
    test2 in 0.33025598526001 Seconds
    testObj1 in 0.38013815879822 Seconds
    testObj2 in 0.3494348526001 Seconds
    
    With 2 Arguments:
    test1 in 0.33168315887451 Seconds
    test2 in 0.35207295417786 Seconds
    testObj1 in 0.37523794174194 Seconds
    testObj2 in 0.38242697715759 Seconds
    
    With 3 Arguments:
    test1 in 0.33901619911194 Seconds
    test2 in 0.36867690086365 Seconds
    testObj1 in 0.41470503807068 Seconds
    testObj2 in 0.3860080242157 Seconds
    
    With 4 Arguments:
    test1 in 0.35170817375183 Seconds
    test2 in 0.39288783073425 Seconds
    testObj1 in 0.39424705505371 Seconds
    testObj2 in 0.39747595787048 Seconds
    
    With 5 Arguments:
    test1 in 0.37077689170837 Seconds
    test2 in 0.59246301651001 Seconds
    testObj1 in 0.41220307350159 Seconds
    testObj2 in 0.60260510444641 Seconds
    

    现在这说明了一个不同的故事。在这种情况下启用了XDebug(但没有覆盖分析,只是启用了扩展),使用交换机优化几乎总是更慢。这很奇怪,因为许多基准测试都是在启用了xdebug的开发盒上运行的。然而,生产箱通常不会与xdebug一起运行。因此,在适当的环境中执行基准测试是一个纯粹的教训。

    来源

    <?php
    
    function benchmark($callback, $iterations, $args) {
        $st = microtime(true);
        $callback($iterations, $args);
        $et = microtime(true);
        $time = $et - $st;
        return $time;
    }
    
    function test() {
    
    }
    
    function test1($iterations, $args) {
        $func = 'test';
        for ($i = 0; $i < $iterations; $i++) {
            call_user_func_array($func, $args);
        }
    }
    
    function test2($iterations, $args) {
        $func = 'test';
        for ($i = 0; $i < $iterations; $i++) {
            switch (count($args)) {
                case 0:
                    $func();
                    break;
                case 1:
                    $func($args[0]);
                    break;
                case 2:
                    $func($args[0], $args[1]);
                    break;
                case 3:
                    $func($args[0], $args[1], $args[2]);
                    break;
                case 4:
                    $func($args[0], $args[1], $args[2], $args[3]);
                    break;
                default:
                    call_user_func_array($func, $args);
            }
        }
    }
    
    class Testing {
    
        public function test() {
    
        }
    
        public function test1($iterations, $args) {
            for ($i = 0; $i < $iterations; $i++) {
                call_user_func_array(array($this, 'test'), $args);
            }
        }
    
        public function test2($iterations, $args) {
            $func = 'test';
            for ($i = 0; $i < $iterations; $i++) {
                switch (count($args)) {
                    case 0:
                        $this->$func();
                        break;
                    case 1:
                        $this->$func($args[0]);
                        break;
                    case 2:
                        $this->$func($args[0], $args[1]);
                        break;
                    case 3:
                        $this->$func($args[0], $args[1], $args[2]);
                        break;
                    case 4:
                        $this->$func($args[0], $args[1], $args[2], $args[3]);
                        break;
                    default:
                        call_user_func_array(array($this, $func), $args);
                }
            }
        }
    
    }
    
    function testObj1($iterations, $args) {
        $obj = new Testing;
        $obj->test1($iterations, $args);
    }
    
    function testObj2($iterations, $args) {
        $obj = new Testing;
        $obj->test2($iterations, $args);
    }
    
    $iterations = 100000;
    
    $results = array('test1' => array(), 'test2' => array(), 'testObj1' => array(), 'testObj2' => array());
    foreach ($results as $callback => &$result) {
        $args = array();
        for ($i = 0; $i < 6; $i++) {
            $result[$i] = benchmark($callback, $iterations, $args);
            $args[] = 'abcdefghijklmnopqrstuvwxyz';
        }
    }
    unset($result);
    $merged = array(0 => array(), 1 => array(), 2 => array(), 3 => array(), 4 => array());
    
    foreach ($results as $callback => $result) {
        foreach ($result as $args => $time) {
            $merged[$args][$callback] = $time;
        }
    }
    
    foreach ($merged as $args => $matrix) {
        echo "With $args Arguments:<br />";
        foreach ($matrix as $callback => $time) {
            echo "$callback in $time Seconds<br />";
        }
        echo "<br />";
    }
    

答案 1 :(得分:1)

您可以在phpsavant模板类中找到它。 PMJ得到了关于call_user_func *()速度有多慢的提示,并且认为前五个参数的90%的工作要快得多。其他任何事情都会以缓慢的方式处理。我无法通过讨论如何找到帖子,但这是他确定问题的页面。 http://paul-m-jones.com/archives/182