在PHP中FOR vs FOREACH的性能

首先,我了解90%的应用程序性能差异是完全不相关的,但我只需要知道哪个是更快的构造。 那和…

网上目前提供的信息令人困惑。 很多人都说foreach是不好的,但是从技术上来说,它应该更快,因为它假设使用迭代器来简化编写数组遍历。 迭代器,再次假设是更快,但在PHP中,也显然死缓(或者这不是一个PHP的东西?)。 我正在谈论数组函数:next()prev()reset()等等,如果它们是函数,而不是那些看起来像函数的PHP语言特性之一。

为了缩小这一点 :我没有兴趣遍历数组的任何超过1的步骤(也没有消极的步骤,即反向迭代)。 我也没有兴趣遍历任意点,只是0到长度。 我也没有看到有定期发生超过1000个键的数组操作,但我确实看到一个数组在应用程序的逻辑中被遍历多次! 对于操作来说,主要只是string操作和回显。

这里有几个参考网站:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php

我到处听到的是:

  • foreach是缓慢的,因此/更快
  • PHP foreach复制它迭代的数组; 为了使它更快,你需要使用引用
  • 代码如下: $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    foreach

这是我的问题。 我写了这个testing脚本: http : //pastebin.com/1ZgK07US ,不pipe我运行多less次脚本,我都得到这样的东西:

 foreach 1.1438131332397 foreach (using reference) 1.2919359207153 for 1.4262869358063 foreach (hash table) 1.5696921348572 for (hash table) 2.4778981208801 

简而言之:

  • foreachforeach快于参考
  • foreach比…更快
  • foreach比hash表快

有人可以解释吗?

  1. 我做错了什么?
  2. PHP的foreach参考事情真的有所作为? 我的意思是为什么如果你通过引用传递它不会复制它?
  3. foreach语句的等价迭代器代码是什么? 我在网上看到了一些,但是每次我testing它们的时机都是一样的。 我也testing了一些简单的迭代器构造,但似乎从来没有得到像样的结果 – PHP中的数组迭代器只是可怕的?
  4. 有更快的方法/方法/构造来迭代FOR / FOREACH(和WHILE)以外的数组吗?

PHP版本5.3.0


编辑:回答在这里的人的帮助下,我能够拼凑出所有问题的答案。 我将在这里总结一下:

  1. “我做错了什么? 共识似乎是:是的,我不能在基准testing中使用回声。 就我个人而言,我仍然没有看到回声是如何随机执行时间的函数,或者其他函数是如何不同的 – 那么脚本的能力比任何事情都更好地产生完全相同的foreach结果是很难的解释,虽然只是“你使用回声”(以及我应该使用什么)。 不过,我承认testing应该用更好的方法来完成。 虽然理想的妥协不会想到。
  2. “PHP的foreach参考文件真的有所作为吗?我的意思是,如果你通过引用传递,它为什么不复制它?” ircmaxell表明,是的,进一步的testing似乎certificate在大多数情况下参考应该更快 – 虽然给我的上面的代码片段,绝对不是全部。 我接受这个问题可能太不直观,不能在这样的水平上打扰,并且需要一些极端的东西,例如反编译来实际地确定哪个更适合每种情况。
  3. “foreach语句的等价迭代器代码是什么;我在网上看到了一些,但是每次testing它们的时机都已经结束;我也testing了一些简单的迭代器结构,但是从来没有得到过像样的结果 – 在PHP中的数组迭代器只是很糟糕? ircmaxell提供了答案, 虽然代码可能只适用于PHP版本> = 5
  4. “有更快的方法/方法/结构来迭代FOR / FOREACH(和WHILE)以外的数组吗? 感谢Gordon的回答。 在PHP5中使用新的数据types应该提供性能提升或内存提升(根据您的情况,这两者可能都是可取的)。 虽然速度方面,很多新types的数组看起来并不比array()好,但splpriorityqueue和splobjectstorage似乎要快很多。 链接由Gordon提供: http : //matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/

谢谢大家的帮助。

我可能会坚持foreach(非参考版本)进行任何简单的遍历。

我个人的看法是使用在上下文中有意义的东西。 就个人而言,我几乎从不使用数组遍历。 我用它来进行其他types的迭代,但是foreach太简单了…在大多数情况下,时间差异是最小的。

值得注意的是:

 for ($i = 0; $i < count($array); $i++) { 

这是一个昂贵的循环,因为它调用每一次迭代。 只要你不这样做,我不认为这真的很重要…

至于引用有所作为,PHP使用了copy-on-write,所以如果你不写入数组,循环的时候会有相对较less的开销。 但是,如果您开始修改数组中的数组,那么您将开始看到它们之间的差异(因为需要复制整个数组,并且引用可以内联修改)。

至于迭代器, foreach相当于:

 $it->rewind(); while ($it->valid()) { $key = $it->key(); // If using the $key => $value syntax $value = $it->current(); // Contents of loop in here $it->next(); } 

只要有更快的迭代方法,这真的取决于问题。 但是我真的需要问,为什么? 我明白想要让事情变得更有效率,但我认为你正在浪费你的时间来进行微观优化。 请记住, Premature Optimization Is The Root Of All Evil

编辑:基于评论,我决定做一个快速的基准运行…

 $a = array(); for ($i = 0; $i < 10000; $i++) { $a[] = $i; } $start = microtime(true); foreach ($a as $k => $v) { $a[$k] = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) { $v = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => $v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n"; 

结果是:

 Completed in 0.0073502063751221 Seconds Completed in 0.0019769668579102 Seconds Completed in 0.0011849403381348 Seconds Completed in 0.00111985206604 Seconds 

所以如果你正在修改循环中的数组,使用引用要快好几倍…

而引用的开销实际上小于复制数组(这是在5.3.2上)…所以它(似乎至less在5.3.2上)好像引用明显更快一样…

我不确定这是如此令人惊讶。 大多数使用PHP编写代码的人都不熟悉PHP在裸机上的实际操作。 我会陈述一些事情,大部分时间都是这样:

  1. 如果你不修改这个variables,那么在PHP中按值更快。 这是因为无论如何都是引用计数,而按价值计算则是不值得的。 它知道你修改ZVAL(PHP的大多数types的内部数据结构)的第二个,它将不得不以直截了当的方式(复制它,忘记其他ZVAL)。 但是你永远不要修改它,所以没关系。 在修改variables时,参考资料会使更复杂的记账工作变得更加复杂。 所以,如果你是只读的,矛盾的是,最好不要与&一起。 我知道,这是违反直觉的,但也是如此。

  2. Foreach不慢。 而对于简单的迭代,它所testing的条件 – “我在这个数组的末尾” – 是使用本地代码完成的,而不是PHP操作码。 即使它是APCcaching的操作码,它仍然比在裸机上执行的一系列本地操作要慢。

  3. 使用for循环for($ i = 0; $ i <count($ x); $ i ++)由于count()和缓慢的PHP能力(或者任何解释语言)时间是否有任何修改数组,这阻止了它计算一次。

  4. 但是即使你用“$ c = count($ x)”来修复它,for $($ i = 0; $ i <$ c; $ i ++)$ i <$ c是一堆Zend操作码, $ i ++。在100000次迭代过程中,这可能很重要,Foreach知道在本地执行什么操作,不需要PHP操作码来testing“我是否在这个数组的最后”条件。

  5. 那么老学校怎么样?“(列表(”东西?好,使用each(),current()等等都会涉及至less1个函数调用,这个函数调用并不慢,但不是免费的。是PHP的操作码!因此,而+列表+每个都有其成本。

由于这些原因,foreach是可以理解的简单迭代的最佳select。

不要忘记,这也是最简单的阅读,所以这是双赢。

在基准testing(尤其是phpbench.com)中要注意的一点是,即使数字是正确的,testing也不是。 phpbench.com上的很多testing都是在做一些小事,滥用PHPcaching数组查找来歪斜基准testing,或者在迭代数组的情况下,实际上并没有在真实世界中testing它(没有人写空循环)。 我已经做了我自己的基准,我发现它是相当反映的真实世界的结果,他们总是显示语言的本地迭代语法foreach出来(惊喜,惊喜)。

 //make a nicely random array $aHash1 = range( 0, 999999 ); $aHash2 = range( 0, 999999 ); shuffle( $aHash1 ); shuffle( $aHash2 ); $aHash = array_combine( $aHash1, $aHash2 ); $start1 = microtime(true); foreach($aHash as $key=>$val) $aHash[$key]++; $end1 = microtime(true); $start2 = microtime(true); while(list($key) = each($aHash)) $aHash[$key]++; $end2 = microtime(true); $start3 = microtime(true); $key = array_keys($aHash); $size = sizeOf($key); for ($i=0; $i<$size; $i++) $aHash[$key[$i]]++; $end3 = microtime(true); $start4 = microtime(true); foreach($aHash as &$val) $val++; $end4 = microtime(true); echo "foreach ".($end1 - $start1)."\n"; //foreach 0.947947025299 echo "while ".($end2 - $start2)."\n"; //while 0.847212076187 echo "for ".($end3 - $start3)."\n"; //for 0.439476966858 echo "foreach ref ".($end4 - $start4)."\n"; //foreach ref 0.0886030197144 //For these tests we MUST do an array lookup, //since that is normally the *point* of iteration //i'm also calling noop on it so that PHP doesn't //optimize out the loopup. function noop( $value ) {} //Create an array of increasing indexes, w/ random values $bHash = range( 0, 999999 ); shuffle( $bHash ); $bstart1 = microtime(true); for($i = 0; $i < 1000000; ++$i) noop( $bHash[$i] ); $bend1 = microtime(true); $bstart2 = microtime(true); $i = 0; while($i < 1000000) { noop( $bHash[$i] ); ++$i; } $bend2 = microtime(true); $bstart3 = microtime(true); foreach( $bHash as $value ) { noop( $value ); } $bend3 = microtime(true); echo "for ".($bend1 - $bstart1)."\n"; //for 0.397135972977 echo "while ".($bend2 - $bstart2)."\n"; //while 0.364789962769 echo "foreach ".($bend3 - $bstart3)."\n"; //foreach 0.346374034882 
  i think but i am not sure ====================== for loop two more operation for checking, increment values. but foreach is first time load data in memory then it will travel next next values.