原因是在call_user_func_array
上有额外的开销。它有一个额外函数调用的开销。通常情况下,这在微秒级别,但在两种情况下可能非常重要:
- 递归函数调用
由于它添加了一个调用到堆栈中,它将使堆栈使用量翻倍。因此,如果堆栈用完会导致应用程序崩溃(使用xdebug或内存约束)。在应用程序(或部分)中,使用这种风格的方法可以将堆栈使用量减少多达33%(这可能是应用程序运行和崩溃之间的区别)
- 性能
如果您经常调用该函数,则这些微秒可能会显著增加。由于这是一个框架(看起来像Lithium完成的某些操作),在应用程序的生命周期中可能会被调用数十次,数百次甚至数千次。因此,尽管每个单独的调用都是微观优化,但效果会显著累加。
因此,是的,您可以删除switch并将其替换为call_user_func_array
,并且在功能方面100%相同。但是,您将失去上述两个优化好处。
编辑并证明性能差异:
我决定自己进行基准测试。下面是我使用的确切源代码链接:
http://codepad.viper-7.com/s32CSb(也包含在本答案底部供参考)
现在,我已经在Linux系统、Windows系统和codepad网站上进行了测试(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个或更少参数的函数,使用switch语句明显更快。对于4个参数,它足够接近,可以作为微小优化。对于5个参数,它会变慢(由于switch语句的开销)。
现在,对象就是另一回事了。对于对象,即使有4个参数,使用switch语句也要快得多。而5个参数则稍微慢一些。
Windows
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个参数的情况下,它对于每种情况都更快(这是可以预料的)。所以这里没有什么不正常的。
Codepad
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(但没有覆盖率分析,只是打开了扩展),使用switch优化几乎总是比较慢的。这很奇怪,因为许多基准测试都是在启用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 />";
}
call_user_func_array()
时,传递参数引用存在问题...请参见https://dev59.com/vkrSa4cB1Zd3GeqPZtQ0 - ken