为了响应ScottJohnson关于可变函数的建议,我决定运行一些基准测试。
编写基准很容易。然而,我看到很多博客条目和邮件列表帖子都在问,“哪个更快?”我的第一个想法总是,“为什么他们不测试并找出答案?”如果我对某些东西如何工作有疑问,我会打开一个临时文件,开始编码,然后运行代码。这是最简单的学习方法。此外,它还教您将事物分解成可管理、可测试的块,并且此代码通常构成以后单元测试的基础。
回到基准测试。Scott问道,“call_user_func
与call_user_func_array
和变量函数语法(即$function_name()
)之间真的有区别吗?”
简短的回答:绝对。长答案?继续阅读。
首先,call_user_func()
和call_user_func_array()
的区别。call_user_func()
当您确切知道您调用的函数或方法需要多少个参数时非常方便,并且即使实际回调发生变化也不会发生变化。这会发挥作用的实例包括调用已建立接口的观察者,并且您知道这些观察者上的被调用方法将始终具有相同数量的参数。此外,使用call_user_func()
,您可以准备好单独传递每个参数:
call_user_func($callback, $arg1, $arg2, $arg3);
但是,如果您不知道自己有多少个参数,或者参数的数量因调用而异怎么办?您将如何构建对call_user_func()
的调用?这就是call_user_func_array()
发挥作用的地方。基本上,call_user_func_array()
只需要两个参数:回调和传递给回调的参数数组:
$callback = 'myFunc'; $args = ('me', 'myself', I'); call_user_func_array($callback, $args);
这被称为:
myFunc('me', 'myself', 'I');
这什么时候方便?当我开发Cgiapp2时,我知道模板引擎通常为其assign()
方法(将变量分配给模板)采用可变数量的参数——一个键和一个值,一个值,或一个关联例如,键/值对数组。由于我事先无法知道参数是什么,我将主题设置为允许可变数量的参数,然后将它们全部传递给观察者:
class myClass { // observer callback public static $observer; function subject() { // get arguments $args = func_get_args(); // call observer with all arguments call_user_func_array(self::$observer, $args); } }
那么,现在,动态函数呢?这些很方便,但可能有些限制:您可以将它们与对象实例方法或定义的函数一起使用,但它们不能与静态方法一起使用。如果您尝试$class::$method
,您将收到意外的T_PAAMAYIM_NEKUDOTAYIM解析器错误。在这种情况下,您必须使用call_user_func()
或call_user_func_array()
。
一切都已完成,让我们来回答Scott的问题:“以这种或另一种方式进行有效率上的好处吗?”
从纯执行时间的角度来看,是的。我运行了以下代码:
class myTest { public static function test() { return true; } public function testMe() { return true; } } function testMe() { return true; } $o = new myTest(); $function = 'testMe'; echo 'Straight function call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { testMe(); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'Dynamic function call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { $function(); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'call_user_func function call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { call_user_func($function); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'call_user_func_array function call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { call_user_func_array($function, null); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'Straight static method call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { myTest::test(); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'call_user_func static method call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { call_user_func(array('myTest', 'test')); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'call_user_func_array static method call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { call_user_func_array(array('myTest', 'test'), null); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'Straight method call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { $o->testMe(); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'call_user_func method call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { call_user_func(array($o, 'testMe')); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n"; echo 'call_user_func_array method call: '; $start = microtime(true); for ($i = 0; $i < 1000000; $i++) { call_user_func_array(array($o, 'testMe'), null); } $end = microtime(true); $elapsed = $end - $start; echo $elapsed, ' secs', "\n";
在我的机器上,它给了我这些结果:
Straight function call: 0.909409046173 secs Dynamic function call: 1.14596605301 secs call_user_func function call: 1.48889017105 secs call_user_func_array function call: 2.02058911324 secs Straight static method call: 0.789363861084 secs call_user_func static method call: 4.42607593536 secs call_user_func_array static method call: 2.98122406006 secs Straight method call: 1.10703587532 secs call_user_func method call: 2.71344089508 secs call_user_func_array method call: 2.56111383438 secs
注意:连续运行几次会产生略有不同的结果;解释将基于运行几次。
- 动态函数调用比直接调用稍慢(前者有一个额外的解释层来确定要调用的函数
call_user_func()
大约是50%较慢,并且call_user_func_array()
比直接函数调用慢大约100%。- 静态和常规方法调用大致等同于函数调用
- call_user_func()通常比
call_user_func_array()
慢,而且较快的操作通常至少是直接调用的执行时间的两倍。
从纯粹的性能角度来看,call_user_func()
和call_user_func_array()
是性能消耗大户。然而,从开发人员的角度来看,它们可以节省大量时间和麻烦:它们可以让您编写灵活的Observer/Subject模式或Decorator模式,这两者都可以使您的类和应用程序更加灵活和可扩展,从而节省您的编码时间晚些。