在PHP中执行FOR vs FOREACH

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

网上目前可用的信息令人困惑。 许多人认为foreach是不好的,但从技术上讲它应该更快,因为它假设使用迭代器来简化编写数组遍历。 迭代器,它们又被认为是更快的,但是在PHP中,它显然已经很慢了(或者这不是PHP的事情?)。 我正在谈论数组函数:next()prev()reset()等等,如果它们是函数,而不是那些看起来像函数的PHP语言功能。

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

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

我到处听到的是:

  • foreach是缓慢的,并且因此for / while更快
  • 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
  • 这是我的问题。 我编写了这个测试脚本:http://pastebin.com/1ZgK07US,无论我运行脚本多少次,我都会得到如下所示的结果:

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

    简而言之:

  • foreachforeach快于参考
  • foreachfor更快
  • foreach快于for一个哈希表
  • 有人可以解释吗?

  • 难道我做错了什么?
  • PHP的foreach参考事物是否真的有所作为? 我的意思是,为什么如果你通过参考传递它不会复制它?
  • foreach语句的等价迭代器代码是什么; 我在网上看到了一些,但每次我测试它们的时机都不合适; 我也测试了一些简单的迭代器结构,但似乎从未得到过像样的结果 - PHP中的数组迭代器只是很糟糕吗?
  • 是否有更快的方法/方法/构造来迭代FOR / FOREACH(和WHILE)以外的数组?
  • PHP版本5.3.0


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

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

    对于任何简单的遍历,我可能会坚持使用foreach(非参考版本)。


    我个人的意见是使用在上下文中有意义的东西。 个人而言,我几乎从来不使用for数组遍历。 我将它用于其他类型的迭代,但是foreach太简单了...在大多数情况下,时间差异将会很小。

    值得注意的是:

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

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

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

    至于迭代器, 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, " Secondsn";
    
    $start = microtime(true);
    foreach ($a as $k => &$v) {
        $v = $v + 1;
    }
    echo "Completed in ", microtime(true) - $start, " Secondsn";
    
    $start = microtime(true);
    foreach ($a as $k => $v) {}
    echo "Completed in ", microtime(true) - $start, " Secondsn";
    
    $start = microtime(true);
    foreach ($a as $k => &$v) {}    
    echo "Completed in ", microtime(true) - $start, " Secondsn";
    

    结果是:

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

    所以如果你在循环中修改数组,使用引用的速度会快几倍......

    而引用的开销实际上低于复制数组(这是在5.3.2上)......因此,它似乎(至少在5.3.2上)好像引用明显更快一样...


    我不确定这是如此令人惊讶。 大多数使用PHP编写代码的人不熟悉PHP实际上在裸机上做的事情。 我将陈述一些事情,大多数情况下都是如此:

  • 如果你没有修改这个变量,那么在PHP中按值更快。 这是因为无论如何它都是引用计数,并且按值计算的数值较少。 它知道你修改ZVAL(大多数类型的PHP内部数据结构)的第二个方法,它将不得不以直截了当的方式将其分开(复制它并忘记其他ZVAL)。 但是你永远不要修改它,所以没关系。 在修改变量时,参考文献使它更复杂,需要做更多的簿记才能知道该怎么做。 所以如果你是只读的,矛盾的是最好不要与&一起出发。 我知道,这是违反直觉的,但也是如此。

  • Foreach不慢。 而对于简单的迭代,它所测试的条件 - “我在这个数组的末尾” - 是使用本地代码完成的,而不是PHP操作码。 即使它是APC缓存的操作码,它仍然比在裸机上完成的一系列本地操作要慢。

  • 使用for循环“for($ i = 0; $ i <count($ x); $ i ++)由于count()以及缺乏PHP的能力(或者任何解释语言)时间是否有任何修改数组,这妨碍了它计算一次。

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

  • 那么老派怎么样“while(list(”stuff?呃,使用each(),current()等等都会涉及至少一个函数调用,这个函数调用并不慢,但不是免费的。是PHP操作码!因此,+列表+每个都有其成本。

  • 由于这些原因,foreach可以理解为简单迭代的最佳选择。

    别忘了,它也是最容易阅读的,所以它是双赢的。


    在基准测试中要注意的一点是(特别是phpbench.com),即使数字是健全的,测试也不是。 phpbench.com上的大部分测试都在做一些微不足道的事情,并且滥用PHP缓存数组查找以歪斜基准测试的能力,或者在迭代数组的情况下实际上并没有在真实世界的情况下测试它(没有人写空循环)。 我已经完成了我自己的基准测试,我发现这些测试基本上反映了真实世界的结果,并且它们总是显示语言的本地迭代语法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
    
    链接地址: http://www.djcxy.com/p/31573.html

    上一篇: Performance of FOR vs FOREACH in PHP

    下一篇: Does C++ compiler recognizes powers of 2?