草庐IT

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

coder 2023-12-30 原文

本周早些时候我看到了一段代码(不幸的是,我无法检索),我很好奇作者是如何实现 __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 语句的充分理由。这里有我似乎没有得到的角度吗?

最佳答案

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

  1. 递归函数调用

    由于它向堆栈添加了另一个调用,因此堆栈使用量会加倍。因此,您可能会遇到问题(使用 xdebug 或内存限制),如果您用完堆栈,这些问题将导致您的应用程序崩溃。在应用程序(或部分)中,使用这种风格的方法可以减少多达 33% 的堆栈使用量(这可能是应用程序运行和崩溃之间的区别)

  2. 表现

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

所以是的,您可以删除开关并将其替换为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 似乎会显着改 rebase 准测试的结果。

这是原始结果:

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 个参数稍微慢一些。

window

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 个或更少的参数,通过 switch 运行它要快得多。对于 5 个参数,使用 switch 会明显变慢。

带有 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 运行。因此,这是在适当环境中执行基准测试的纯类(class)。

来源

<?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 />";
}

关于php - 使用显式参数调用函数与 call_user_func_array(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8343399/

有关php - 使用显式参数调用函数与 call_user_func_array()的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  9. ruby - 在 Ruby 中实现 `call_user_func_array` - 2

    我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)

  10. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

随机推荐