为什么在PHP中进行类型转换并比较比is_*函数更快?

12

在优化一个 PHP 函数时,我进行了修改

if(is_array($obj)) foreach($obj as $key=>$value { [snip] } 
else if(is_object($obj)) foreach($obj as $key=>$value { [snip] } 
if($obj == (array) $obj) foreach($obj as $key=>$value { [snip] } 
else if($obj == (obj) $obj) foreach($obj as $key=>$value { [snip] } 

在学习了 === 后,我将其改为

if($obj === (array) $obj) foreach($obj as $key=>$value { [snip] } 
else if($obj === (obj) $obj) foreach($obj as $key=>$value { [snip] } 

将每个测试从is_*更改为转换类型可以大大提高速度(>30%)。

我理解 === 比 == 更快,因为不需要做强制转换,但是为什么将变量转换类型会比调用任何is_*函数都快这么多呢?

编辑: 由于每个人都在问正确性,我写了这个小测试:

$foo=(object) array('bar'=>'foo');
$bar=array('bar'=>'foo');

if($foo===(array) $foo) echo '$foo is an array?';
if($bar===(object) $bar) echo '$bar is an object?';
它并没有打印出任何错误,两个变量都没有改变,所以我认为它是有效的,但我准备接受其他说法。 另一个编辑: Artefacto的程序给了我以下数字:
PHP 5.3.2-1ubuntu4.2(64位)在Core i5-750上使用Xdebug Elapsed (1): 0.46174287796021 / 0.28902506828308 Elapsed (2): 0.52625703811646 / 0.3072669506073 Elapsed (3): 0.57169318199158 / 0.12708187103271 Elapsed (4): 0.51496887207031 / 0.30524897575378 猜想:强制类型转换和比较可以快1.7-4倍。
PHP 5.3.2-1ubuntu4.2(64位)在不使用Xdebug的Core i5-750上 Elapsed (1): 0.15818405151367 / 0.214271068573 Elapsed (2): 0.1531388759613 / 0.25853085517883 Elapsed (3): 0.16164898872375 / 0.074632883071899 Elapsed (4): 0.14408397674561 / 0.25812387466431 没有使用Xdebug,额外的函数调用就不再重要了,因此每个测试(除了第3个)都运行得更快。
PHP 5.3.2-1ubuntu4.2在Pentium M 1.6GHz上 Elapsed (1): 0.97393798828125 / 0.9062979221344 Elapsed (2): 0.39448714256287 / 0.86932587623596 Elapsed (3): 0.44513893127441 / 0.23662400245667 Elapsed (4): 0.38685202598572 / 0.82854390144348 猜想:转换数组更慢,转换对象可能更快,但可能不会更慢。
PHP 5.2.6-1+lenny8在Xeon 5110上 Elapsed (1): 0.273758888245 / 0.530702114105 Elapsed (2): 0.276469945908 / 0.605964899063 Elapsed (3): 0.332523107529 / 0.137730836868 Elapsed (4): 0.267735004425 / 0.556323766708 猜想:这些结果与Artefacto的结果类似,我认为这是PHP 5.2。 解决方案: 我使用的分析工具(Xdebug)使函数调用变慢了约3倍(即使不进行分析),但并没有明显影响强制类型转换和比较,因此强制类型转换和比较似乎更快,尽管它只是没有受到调试器/分析器的影响。

4
如果我不得不猜的话,可能是为了避免函数调用的开销。 - Amber
2
您的替换结果并不会得到一个等效的程序。例如,如果$obj是一个对象,is_array($obj)返回false,但是可能会出现($obj == (array) $obj)评估为true的情况。 - Artefacto
@Artefacto 是的,那完全正确。那就是我写的,麻烦您看一下我的答案,并给我一些反馈。 - streetparade
@Artefacto 即使是空数组/对象,我的示例代码也能正常工作。 - tstenner
1
@tstenner 你的例子只是一个例子而已。它并没有说明一般情况。你的例子之所以有效,是因为你正在使用具有默认cast_object处理程序的对象(参见http://wiki.php.net/internals/engine/objects#cast_object),当使用`==`运算符将对象与任何其他类型进行比较时,会调用该处理程序。当目标是数组时,此处理程序总是失败的,因此将对象与数组进行比较总是失败的。但是一般情况下,你不能保证 is_array($obj) === false 意味着 ($obj == (array) $obj) === true。(我在谈论== - Artefacto
显示剩余4条评论
3个回答

4

我无法真正重现这个问题。实际上,您的策略在所有情况下都给了我更长的时间,除了一个例外:

<?php

class A {
    private $a = 4;
    private $b = 4;
    private $f = 7;
}

$arr = array("a" => 4, "b" => 4, "f" => 7);

$obj = new A();

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    is_array($obj) and die("err");
}

echo "Elapsed (1.1): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    ($obj === (array) $obj) and die("err");
}

echo "Elapsed (1.2): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    is_object($arr) and die("err");
}

echo "Elapsed (2.1): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    ($arr === (object) $arr) and die("err");
}

echo "Elapsed (2.2): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    is_object($obj) or die("err");
}

echo "Elapsed (3.1): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    ($obj === (object) $obj) or die("err");
}

echo "Elapsed (3.2): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    is_array($arr) or die("err");
}

echo "Elapsed (4.1): " . (microtime(true) - $t);
echo "\n";

$t = microtime(true);

for ($i = 0; $i < 500000; $i++) {
    ($arr === (array) $arr) or die("err");
}

echo "Elapsed (4.2): " . (microtime(true) - $t);

输出:

已过时间 (1.1): 0.366055965424
已过时间 (1.2): 0.550662994385
已过时间 (2.1): 0.337422132492
已过时间 (2.2): 0.579686880112
已过时间 (3.1): 0.402997970581
已过时间 (3.2): 0.190818071365
已过时间 (4.1): 0.332742214203
已过时间 (4.2): 0.549873113632

强制类型转换和比较只能更快地检查某些东西是否为对象。 推测如下:可能是因为对象身份检查仅需要确定处理程序表和对象句柄是否相同,而检查数组身份则需要在最坏情况下比较所有值。


Xdebug影响了我的数据,当我比较i5和Xeon时发现了问题。我接受了你的答案,但你可能需要添加一条注释,说明PHP版本和插件(调试器、opcode缓存等)会影响基准测试。 - tstenner
@tstenner 使用 Windows 7 64 位操作系统,搭配 Athlon 64 3500+ 处理器,编译于 VC6 的 PHP 5.3.2 发布版本。未使用调试器或 Opcode 缓存(即使使用了 Opcode 缓存也不会有任何影响;时间统计是在脚本编译后进行的)。 - Artefacto

3

我想补充一点,对于小型数组/对象来说,比较转换速度更快。

for ($i = 0; $i < 10000; $i ++) {
   $arr[] = $i;
}

$t = microtime(true);
for ($i = 0; $i < 5000; $i ++) {
   is_array($arr);
}
echo "Elapsed: " . (microtime(true) - $t) . "\n";

$t = microtime(true);
for ($i = 0; $i < 5000; $i ++) {
   ($arr === (array)$arr);
}
echo "Elapsed: " . (microtime(true) - $t) . "\n";

输出:

Elapsed: 0.0487940311432
Elapsed: 9.20055603981

3
  1. 在某些PHP版本中,函数调用可能比转换要慢。
  2. 调试器/分析工具会显著改变这些关系,因为它们覆盖了函数调用处理程序等,所以它们会改变调用函数的时间。
  3. 如果您正在优化这些内容,那么您正在错误的地方寻找。如果您的PHP应用程序执行某些非平凡操作,那么它的性能几乎不会从微秒级优势中提高,您可能会尝试超越引擎,即使它确实可以提高性能,但它很可能会随着下一个PHP版本而消失,因为下一个版本将在某些细节上更改引擎实现。例如,5.2和5.3引擎在内部有所不同,并且下一个版本将有更多的不同之处。这些差异通常不会影响代码,但它们会使您所有的微小优化变得无关紧要。
  4. 将不是对象/数组的内容转换为对象/数组可能比一般检查要慢,因为它需要创建一个新的对象/数组。

一微秒可能看起来不算多,但这个测试占用了函数执行时间的四分之一以上。 - tstenner
  1. 这也让我感到惊讶,这就是我问这个问题的原因;)无论如何,回答很好。+1
- tstenner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接