在PHP中FOR FOR FOR FORACH的性能

时间:2010-08-07 11:15:09

标签: php arrays performance foreach for-loop

首先,我理解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++)
    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

简而言之:

  • foreach比参考
  • foreach
  • foreachfor
  • foreach比哈希表的for

有人可以解释一下吗?

  1. 我做错了吗?
  2. PHP foreach引用的东西真的有所作为吗?我的意思是,如果你通过引用传递它为什么不复制呢?
  3. foreach语句的等效迭代器代码是什么;我在网上看到了一些,但每次我测试它们的时机都是关闭的;我还测试了一些简单的迭代器结构,但似乎从来没有得到过不错的结果 - PHP中的数组迭代器是不是很糟糕?
  4. 是否有更快的方法/方法/构造来迭代除FOR / FOREACH(和WHILE)之外的数组?
  5. PHP版本5.3.0

    <小时/> 修改:回答 在这里的人们的帮助下,我能够拼凑出所有问题的答案。我在这里总结一下:

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

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

5 个答案:

答案 0 :(得分:100)

我个人的意见是使用在上下文中有意义的东西。就个人而言,我几乎从不使用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, " 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)...所以它出现(至少在5.3.2上)好像引用明显更快......

答案 1 :(得分:49)

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

  1. 如果您没有修改变量,PHP中的按值会更快。这是因为无论如何都要计算它的参考值,并且按值给它做的更少。它知道第二个你修改ZVAL(PHP的大多数类型的内部数据结构),它必须以一种简单的方式将其分解(复制它并忘记其他ZVAL)。但你永远不会修改它,所以没关系。当您修改变量时,引用会使得知道要做什么时需要做更多的簿记。因此,如果你是只读的,那么矛盾的是,最好不要用&amp ;.我知道,这是违反直觉的,但也是如此。

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

  3. 使用for循环“for($ i = 0; $ i&lt; count($ x); $ i ++)因为count()而缺乏PHP的能力(或实际上是解释语言)在解析时评估是否有任何修改数组。这可以防止它一次评估计数。

  4. 但即使你用“$ c = count($ x); for($ i = 0; $ i&lt; $ c; $ i ++)修复它,$ i&lt; $ c也是一堆Zend最好的操作码,就像$ i ++一样。在100000次迭代过程中,这很重要.Toreach在本机级别知道该怎么做。没有PHP操作码需要测试“我在这个数组的末尾”条件。

  5. 旧学校怎么样“while(列表(”东西?好吧,使用each(),current()等等都将涉及至少1个函数调用,这不是很慢,但是不是免费的。是的,这些都是PHP操作码!所以虽然+ list +每个都有它的成本。

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

    不要忘记,它也是最容易阅读的,所以它是双赢的。

答案 2 :(得分:28)

在基准测试(尤其是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

答案 3 :(得分:3)

现在是2020年,随着php 7.4和opcache的发展,东西有了很大的发展。

这是OP ^基准,以unix CLI 的身份运行,没有回显和html部分。

测试在常规计算机上本地运行。

php -v

PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )

修改后的基准脚本:

<?php 
 ## preperations; just a simple environment state

  $test_iterations = 100;
  $test_arr_size = 1000;

  // a shared function that makes use of the loop; this should
  // ensure no funny business is happening to fool the test
  function test($input)
  {
    //echo '<!-- '.trim($input).' -->';
  }

  // for each test we create a array this should avoid any of the
  // arrays internal representation or optimizations from getting
  // in the way.

  // normal array
  $test_arr1 = array();
  $test_arr2 = array();
  $test_arr3 = array();
  // hash tables
  $test_arr4 = array();
  $test_arr5 = array();

  for ($i = 0; $i < $test_arr_size; ++$i)
  {
    mt_srand();
    $hash = md5(mt_rand());
    $key = substr($hash, 0, 5).$i;

    $test_arr1[$i] = $test_arr2[$i] = $test_arr3[$i] = $test_arr4[$key] = $test_arr5[$key]
      = $hash;
  }

  ## foreach

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    foreach ($test_arr1 as $k => $v)
    {
      test($v);
    }
  }
  echo 'foreach '.(microtime(true) - $start)."\n";  

  ## foreach (using reference)

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    foreach ($test_arr2 as &$value)
    {
      test($value);
    }
  }
  echo 'foreach (using reference) '.(microtime(true) - $start)."\n";

  ## for

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    $size = count($test_arr3);
    for ($i = 0; $i < $size; ++$i)
    {
      test($test_arr3[$i]);
    }
  }
  echo 'for '.(microtime(true) - $start)."\n";  

  ## foreach (hash table)

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    foreach ($test_arr4 as $k => $v)
    {
      test($v);
    }
  }
  echo 'foreach (hash table) '.(microtime(true) - $start)."\n";

  ## for (hash table)

  $start = microtime(true);
  for ($j = 0; $j < $test_iterations; ++$j)
  {
    $keys = array_keys($test_arr5);
    $size = sizeOf($test_arr5);
    for ($i = 0; $i < $size; ++$i)
    {
      test($test_arr5[$keys[$i]]);
    }
  }
  echo 'for (hash table) '.(microtime(true) - $start)."\n";

输出:

foreach 0.0032877922058105
foreach (using reference) 0.0029420852661133
for 0.0025191307067871
foreach (hash table) 0.0035080909729004
for (hash table) 0.0061779022216797

如您所见,进化是疯狂的,比2012年的报道快了 560倍

在我的机器和服务器上,经过大量的实验,循环的基础知识是最快的。使用嵌套循环( $ i $ j $ k ..)

它也是用法上最灵活的,并且从我的角度看具有更好的可读性。

答案 4 :(得分:1)

我想但我不确定:for循环需要两个操作来检查和递增值。 foreach将数据加载到内存中,然后它将迭代每个值。