为了响应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模式,这两者都可以使您的类和应用程序更加灵活和可扩展,从而节省您的编码时间晚些。
