PHP中FOR与FOREACH的性能比较

pbossiut  于 2023-11-16  发布在  PHP
关注(0)|答案(5)|浏览(106)

首先,我知道在90%的应用程序中,性能差异是完全无关紧要的,但我只需要知道哪种结构更快。
目前网上关于它们的信息令人困惑。很多人说foreach很糟糕,但从技术上讲,它应该更快,因为它应该简化使用迭代器编写数组遍历的过程。迭代器,也应该更快,但在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

字符串
简而言之:

  • foreachforeach更快,带参考
  • foreachfor
  • 对于哈希表,foreachfor

有人能解释一下吗
1.我做错什么了吗?

  1. PHP的foreach引用真的有区别吗?我的意思是,如果你通过引用传递,为什么它不复制它呢?
  2. foreach语句的等效迭代器代码是什么?我在网上看到过一些,但每次测试它们的时间都相差很远;我也测试了一些简单的迭代器结构,但似乎从来没有得到像样的结果--PHP中的数组迭代器只是糟糕吗?
    1.除了FOR/FOREACH(和WHILE)之外,是否有更快的方法/方法/结构来遍历数组?
    PHP版本5.3.0

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

1.* “我做错了什么吗?"* 共识似乎是:是的,我不能在基准测试中使用echo。就我个人而言,我仍然不明白echo是一个随机执行时间的函数,或者其他函数是如何不同的--那个脚本生成与foreach完全相同的结果的能力比一切都好,这很难解释,尽管只是“你在使用echo”。(好吧,我应该一直使用什么)。然而,我承认测试应该用更好的东西来完成;尽管理想的妥协并没有浮现在脑海中。
1.* “PHP的foreach引用真的有区别吗?我的意思是,如果你通过引用传递,为什么它不会复制它?“* ircmaxell表明是的,进一步的测试似乎证明在大多数情况下引用应该更快--尽管给出了我上面的代码片段,大多数绝对不意味着全部。我接受这个问题可能太不-直观的麻烦,在这样的水平,将需要一些极端的东西,如反编译,以实际确定哪一个是更好的每一种情况。
1.* “foreach语句的等效迭代器代码是什么?我在网上看到过一些,但每次测试时,时间都相差很远;我也测试过一些简单的迭代器结构,但似乎从未得到像样的结果--PHP中的数组迭代器很糟糕吗?"* ircmaxell提供了下面的答案;尽管代码可能只对PHP版本>= 5有效。
1.* “除了FOR/FOREACH之外,是否有更快的方法/方法/结构来遍历数组(WHILE)?“* 感谢Gordon提供的答案。在PHP 5中使用新的数据类型应该会给予性能提升或内存提升(根据你的情况,这两种方法都是可取的)。虽然速度方面,很多新类型的数组似乎并不比array()好,splpriorityqueue和splobjectstorage看起来确实要快得多。
对于任何简单的遍历,我可能会坚持使用foreach(非引用版本)。

fzwojiic

fzwojiic1#

我个人的观点是使用在上下文中有意义的东西。我个人几乎从不使用for进行数组遍历。我使用它进行其他类型的迭代,但foreach太容易了......在大多数情况下,时间差将是最小的。
值得注意的是:

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

字符串
这是一个开销很大的循环,因为它在每一次迭代中都调用了count。只要你不这样做,我认为这并不重要。
至于引用的作用,PHP使用写时复制,所以如果你不写数组,在循环时会有相对较小的开销。但是,如果你开始在数组中修改数组,这就是你开始看到它们之间的差异的地方(因为一个需要复制整个数组,引用可以直接修改内联)。
对于迭代器,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上)引用要快得多.

**编辑:**使用PHP 8.0我得到了以下内容:

Completed in 0.0005030632019043 Seconds
Completed in 0.00066304206848145 Seconds
Completed in 0.00016379356384277 Seconds
Completed in 0.00056815147399902 Seconds


多次重复该测试,排名结果一致。

zxlwwiss

zxlwwiss2#

我不确定这是否令人惊讶。大多数用PHP编写代码的人并不精通PHP在裸机上的实际功能。我将陈述几件事,这在大多数情况下都是正确的:
1.如果你不修改变量,在PHP中按值方式会更快。这是因为它的引用计数无论如何,按值方式让它做的更少。它知道你修改ZVAL的第二次(PHP的内部数据结构的大多数类型),它将不得不打破它在一个简单的方式(复制它,忘记其他ZVAL)。但你永远不会修改它,所以这并不重要。参考文献使这变得更加复杂,因为它必须做更多的簿记,才能知道该做什么。当你修改变量的时候,如果你是只读的,矛盾的是,最好不要用&,我知道,这是违反直觉的,但这也是正确的。

  1. Foreach并不慢。对于简单的迭代,它测试的条件-“我在这个数组的末尾吗”-是使用本机代码完成的,而不是PHP操作码。即使它是APC缓存的操作码,它仍然比在裸机上完成的一堆本机操作慢。
    1.使用for循环“for($i=0; $i < count($x); $i++)是很慢的,因为count(),以及PHP缺乏在解析时评估是否有任何东西修改数组的能力(或者实际上任何解释语言)。
    1.但是,即使你用“$c=count($x); for($i=0; $i<$c; $i++)”修复了它,$i<$c充其量也只是一堆Zend操作码,$i++也是如此。在100000次迭代的过程中,这可能很重要。Foreach在本机级别知道该做什么。不需要PHP操作码来测试“我在这个数组的末尾吗”条件。
    1.那么老的“while(list(“东西呢?好吧,使用each(),current()等都将涉及至少一个函数调用,这并不慢,但不是免费的。是的,这些又是PHP操作码!所以while + list + each也有它的成本。
    出于这些原因,foreach是简单迭代的最佳选择。
    别忘了,它也是最容易阅读的,所以这是双赢的。
wvt8vs2t

wvt8vs2t3#

在基准测试中需要注意的一件事(尤其是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

字符串

mm5n2pyu

mm5n2pyu4#

现在是2020年,随着php 7.4和opcache的出现,东西有了很大的发展。
这里是OP^基准测试,作为unixCLI运行,没有echo和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.)
它也是最灵活的使用,并有一个更好的可读性,从我的看法。

w7t8yxp5

w7t8yxp55#

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

相关问题