PHP 中 FOR 与 FOREACH 的性能
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3430194/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Performance of FOR vs FOREACH in PHP
提问by srcspider
First of all, I understand in 90% of applications the performance difference is completely irrelevant, but I just need to know which is the faster construct. That and...
首先,我知道在 90% 的应用程序中性能差异完全无关紧要,但我只需要知道哪个是更快的构造。那和...
The information currently available on them on the net is confusing. A lot of people say foreach is bad, but technically it should be faster since it's suppose to simplify writing a array traversal using iterators. Iterators, which are again suppose to be faster, but in PHP are also apparently dead slow (or is this not a PHP thing?). I'm talking about the array functions: next() prev() reset() etc. well, if they are even functions and not one of those PHP language features that look like functions.
目前网上关于它们的信息令人困惑。很多人说 foreach 不好,但从技术上讲它应该更快,因为它假设使用迭代器简化编写数组遍历的过程。迭代器,再次假设更快,但在 PHP 中显然也很慢(或者这不是 PHP 的东西?)。我说的是数组函数:next() prev() reset() 等等,如果它们是函数,而不是那些看起来像函数的 PHP 语言特性之一。
To narrow this down a little: I'm not interesting in traversing arrays in steps of anything more than 1 (no negative steps either, ie. reverse iteration). I'm also not interested in a traversal to and from arbitrary points, just 0 to length. I also don't see manipulating arrays with more than 1000 keys happening on a regular basis, but I do see a array being traversed multiple times in the logic of a application! Also as for operations, largely only string manipulation and echo'ing.
稍微缩小一下范围:我对以超过 1 的任何步长遍历数组并不感兴趣(也没有负步,即反向迭代)。我也对往返任意点的遍历不感兴趣,只是 0 到长度。我也没有看到定期发生超过 1000 个键的操作数组,但我确实看到在应用程序的逻辑中多次遍历数组!同样对于操作,主要只有字符串操作和回显。
Here are few reference sites:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php
这里有几个参考站点:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php
What I hear everywhere:
我到处听到的:
foreach
is slow, and thusfor
/while
is faster- PHPs
foreach
copies the array it iterates over; to make it faster you need to use references - code like this:
$key = array_keys($aHash); $size = sizeOf($key);
is faster than a
for ($i=0; $i < $size; $i++)foreach
foreach
很慢,因此for
/while
更快- PHPs
foreach
复制它迭代的数组;为了使它更快,您需要使用引用 - 像这样的代码:比a快
$key = array_keys($aHash); $size = sizeOf($key);
for ($i=0; $i < $size; $i++)foreach
Here's my problem. I wrote this test script: http://pastebin.com/1ZgK07USand no matter how many times I run the script, I get something like this:
这是我的问题。我写了这个测试脚本: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
In short:
简而言之:
foreach
is faster thanforeach
with referenceforeach
is faster thanfor
foreach
is faster thanfor
for a hash table
foreach
比foreach
参考更快foreach
比for
foreach
for
比哈希表快
Can someone explain?
有人可以解释一下吗?
- Am I doing something wrong?
- Is PHP foreach reference thing really making a difference? I mean why would it not copy it if you pass by reference?
- What's the equivalent iterator code for the foreach statement; I've seen a few on the net but each time I test them the timing is way off; I've also tested a few simple iterator constructs but never seem to get even decent results -- are the array iterators in PHP just awful?
- Are there faster ways/methods/constructs to iterate though a array other than FOR/FOREACH (and WHILE)?
- 难道我做错了什么?
- PHP foreach 参考的东西真的有所作为吗?我的意思是如果你通过引用传递它为什么不复制它?
- foreach 语句的等效迭代器代码是什么?我在网上看到过一些,但每次我测试它们时,时间都相差甚远;我还测试了一些简单的迭代器构造,但似乎从来没有得到过像样的结果——PHP 中的数组迭代器是不是很糟糕?
- 是否有更快的方法/方法/结构来迭代除 FOR/FOREACH(和 WHILE)以外的数组?
PHP Version 5.3.0
PHP 版本 5.3.0
Edit: Answer编辑:回答在这里人们的帮助下,我能够拼凑出所有问题的答案。我将在这里总结它们:
- "Am I doing something wrong?"The consensus seems to be: yes, I can't use echo in benchmarks. Personally, I still don't see how echo is some function with random time of execution or how any other function is somehow any different -- that and the ability of that script to just generate the exact same results of foreach better than everything is hard to explain though just "you're using echo" (well what should I have been using). However, I concede the test should be done with something better; though a ideal compromise does not come to mind.
- "Is PHP foreach reference thing really making a difference? I mean why would it not copy it if you pass by reference?"ircmaxell shows that yes it is, further testing seems to prove in most cases reference should be faster -- though given my above snippet of code, most definitely doesn't mean all. I accept the issue is probably too non-intuitive to bother with at such a level and would require something extreme such as decompiling to actually determine which is better for each situation.
- "What's the equivalent iterator code for the foreach statement; I've seen a few on the net but each time I test them the timing is way off; I've also tested a few simple iterator constructs but never seem to get even decent results -- are the array iterators in PHP just awful?"ircmaxell provided the answer bellow; though the code might only be valid for PHP version >= 5
- "Are there faster ways/methods/constructs to iterate though a array other than FOR/FOREACH (and WHILE)?"Thanks go to Gordon for the answer. Using new data types in PHP5 should give either a performance boost or memory boost (either of which might be desirable depending on your situation). While speed wise a lot of the new types of array don't seem to be better than array(), the splpriorityqueue and splobjectstorage do seem to be substantially faster. Link provided by Gordon: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/
- “难道我做错了什么?” 共识似乎是:是的,我不能在基准测试中使用 echo。就我个人而言,我仍然不明白 echo 是某个具有随机执行时间的函数,或者任何其他函数有何不同——以及该脚本生成完全相同的 foreach 结果的能力比一切都好解释虽然只是“你正在使用回声”(我应该使用什么)。但是,我承认测试应该用更好的方法来完成。虽然没有想到理想的妥协。
- “PHP foreach 引用的东西真的有所作为吗?我的意思是,如果您通过引用传递,它为什么不复制它?” ircmaxell 表明是的,进一步的测试似乎证明在大多数情况下参考应该更快——尽管鉴于我上面的代码片段,绝对不是全部。我接受这个问题可能太不直观了,无法在这样的级别上打扰,并且需要一些极端的东西,例如反编译来实际确定哪种情况更适合每种情况。
- “foreach 语句的等效迭代器代码是什么;我在网上看到了一些,但每次我测试它们时,时间都相差甚远;我还测试了一些简单的迭代器结构,但似乎从未得到过像样的结果-- PHP 中的数组迭代器很糟糕吗?” ircmaxell 在下面提供了答案;虽然代码可能只对 PHP 版本 >= 5 有效
- “是否有更快的方法/方法/结构来迭代除 FOR/FOREACH(和 WHILE)以外的数组?” 感谢戈登的回答。在 PHP5 中使用新的数据类型应该可以提高性能或提高内存(根据您的情况,这两者都可能是可取的)。虽然在速度方面,许多新类型的数组似乎并不比 array() 好,但 splpriorityqueue 和 splobjectstorage 似乎要快得多。Gordon 提供的链接:http: //matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/
Thank you everyone who tried to help.
感谢所有试图提供帮助的人。
I'll likely stick to foreach (the non-reference version) for any simple traversal.
对于任何简单的遍历,我可能会坚持使用 foreach(非参考版本)。
采纳答案by ircmaxell
My personal opinion is to use what makes sense in the context. Personally I almost never use for
for array traversal. I use it for other types of iteration, but foreach
is just too easy... The time difference is going to be minimal in most cases.
我个人的意见是使用上下文中有意义的内容。我个人几乎从不for
用于数组遍历。我将它用于其他类型的迭代,但foreach
太简单了......在大多数情况下,时间差异将很小。
The big thing to watch for is:
需要注意的大事是:
for ($i = 0; $i < count($array); $i++) {
That's an expensive loop, since it calls count on every single iteration. So long as you're not doing that, I don't think it really matters...
这是一个昂贵的循环,因为它在每次迭代时调用计数。只要你不这样做,我认为这并不重要......
As for the reference making a difference, PHP uses copy-on-write, so if you don't write to the array, there will be relatively little overhead while looping. However, if you start modifying the array within the array, that's where you'll start seeing differences between them (since one will need to copy the entire array, and the reference can just modify inline)...
至于引用有所不同,PHP使用copy-on-write,所以如果不写入数组,循环时开销相对较小。但是,如果您开始修改数组中的数组,那么您将开始看到它们之间的差异(因为需要复制整个数组,而引用只能修改内联)...
As for the iterators, foreach
is equivalent to:
至于迭代器,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();
}
As far as there being faster ways to iterate, it really depends on the problem. But I really need to ask, why? I understand wanting to make things more efficient, but I think you're wasting your time for a micro-optimization. Remember, Premature Optimization Is The Root Of All Evil
...
至于是否有更快的迭代方法,这实际上取决于问题。但我真的需要问,为什么?我理解想让事情变得更有效率,但我认为你是在浪费时间进行微优化。记住,Premature Optimization Is The Root Of All Evil
...
Edit:Based upon the comment, I decided to do a quick benchmark run...
编辑:根据评论,我决定做一个快速的基准测试......
$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";
And the results:
结果:
Completed in 0.0073502063751221 Seconds
Completed in 0.0019769668579102 Seconds
Completed in 0.0011849403381348 Seconds
Completed in 0.00111985206604 Seconds
So if you're modifying the array in the loop, it's several times faster to use references...
因此,如果您在循环中修改数组,则使用引用要快几倍......
And the overhead for just the reference is actually less than copying the array (this is on 5.3.2)... So it appears (on 5.3.2 at least) as if references are significantly faster...
而仅仅引用的开销实际上比复制数组要少(这是在 5.3.2 上)......所以它看起来(至少在 5.3.2 上)好像引用明显更快......
回答by Jaimie Sirovich
I'm not sure this is so surprising. Most people who code in PHP are not well versed in what PHP is actually doing at the bare metal. I'll state a few things, which will be true most of the time:
我不确定这是否如此令人惊讶。大多数使用 PHP 编码的人并不精通 PHP 在裸机上实际执行的操作。我将陈述一些事情,这在大多数情况下都是正确的:
If you're not modifying the variable, by-value is faster in PHP. This is because it's reference counted anyway and by-value gives it less to do. It knows the second you modify that ZVAL (PHP's internal data structure for most types), it will have to break it off in a straightforward way (copy it and forget about the other ZVAL). But you never modify it, so it doesn't matter. References make that more complicated with more bookkeeping it has to do to know what to dowhen you modify the variable. So if you're read-only, paradoxically it's better not the point that out with the &. I know, it's counter intuitive, but it's also true.
Foreach isn't slow. And for simple iteration, the condition it's testing against — "am I at the end of this array" — is done using native code, not PHP opcodes. Even if it's APC cached opcodes, it's still slower than a bunch of native operations done at the bare metal.
Using a for loop "for ($i=0; $i < count($x); $i++) is slow because of the count(), and the lack of PHP's ability (or really any interpreted language) to evaluate at parse time whether anything modifies the array. This prevents it from evaluating the count once.
But even once you fix it with "$c=count($x); for ($i=0; $i<$c; $i++) the $i<$c is a bunch of Zend opcodes at best, as is the $i++. In the course of 100000 iterations, this can matter. Foreach knows at the native level what to do. No PHP opcodes needed to test the "am I at the end of this array" condition.
What about the old school "while(list(" stuff? Well, using each(), current(), etc. are all going to involve at least 1 function call, which isn't slow, but not free. Yes, those are PHP opcodes again! So while + list + each has its costs as well.
如果您不修改变量,PHP 中的按值会更快。这是因为无论如何它都是引用计数的,而按值则让它少做。它知道您修改 ZVAL(大多数类型的 PHP 内部数据结构)的那一刻,它必须以直接的方式将其断开(复制它并忘记其他 ZVAL)。但是你永远不会修改它,所以没关系。引用使这变得更加复杂,因为它必须做更多的簿记才能知道在修改变量时要做什么。所以如果你是只读的,矛盾的是最好不要用 & 来解决这个问题。我知道,这是违反直觉的,但这也是事实。
Foreach 并不慢。对于简单的迭代,它测试的条件——“我在这个数组的末尾”——是使用本机代码完成的,而不是 PHP 操作码。即使它是 APC 缓存的操作码,它仍然比在裸机上完成的一堆本机操作慢。
使用 for 循环 "for ($i=0; $i < count($x); $i++) 很慢,因为 count(),并且缺乏 PHP 的能力(或实际上任何解释语言)在解析时进行评估time 是否有任何修改数组。这会阻止它评估一次计数。
但即使你用 "$c=count($x); for ($i=0; $i<$c; $i++) 修复它, $i<$c 充其量也是一堆 Zend 操作码,就像这样$i++。在 100000 次迭代的过程中,这很重要。Foreach 在本机级别知道要做什么。不需要 PHP 操作码来测试“我在这个数组的末尾”条件。
老派的“while(list(”) 怎么样?嗯,使用 each()、current() 等都将涉及至少 1 个函数调用,这并不慢,但不是免费的。是的,那些又是 PHP 操作码!所以 while + list + each 也有它的成本。
For these reasons foreach is understandably the best option for simple iteration.
由于这些原因,foreach 是简单迭代的最佳选择,这是可以理解的。
And don't forget, it's also the easiest to read, so it's win-win.
并且不要忘记,它也是最容易阅读的,所以它是双赢的。
回答by Kendall Hopkins
One thing to watch out for in benchmarks (especially phpbench.com), is even though the numbers are sound, the tests are not. Alot of the tests on phpbench.com are doing things at are trivial and abuse PHP's ability to cache array lookups to skew benchmarks or in the case of iterating over an array doesn't actually test it in real worldcases (no one writes empty for loops). I've done my own benchmarks that I've found are fairly reflective of the real world results and they alwaysshow the language's native iterating syntax foreach
coming out on top (surprise, surprise).
在基准测试(尤其是 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
回答by Eswer
I think but I am not sure : the for
loop takes two operations for checking and incrementing values. foreach
loads the data in memory then it will iterate every values.
我想但我不确定:for
循环需要两个操作来检查和递增值。foreach
将数据加载到内存中,然后它会迭代每个值。
回答by NVRM
It's 2020 and stuffs had greatly evolved with php 7.4 and opcache.
现在是 2020 年,随着 php 7.4 和opcache 的出现,东西有了很大的发展。
Here is the OP^ benchmark, ran as unix CLI, without the echo and html parts.
这是 OP^ 基准,作为 unix CLI 运行,没有 echo 和 html 部分。
Test ran locally on a regular computer.
测试在普通计算机上本地运行。
php -v
PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
Modified benchmark script:
修改后的基准脚本:
<?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";
Output:
输出:
foreach 0.0032877922058105
foreach (using reference) 0.0029420852661133
for 0.0025191307067871
foreach (hash table) 0.0035080909729004
for (hash table) 0.0061779022216797
As you can see the evolution is insane, about 560 time fasterthan reported in 2012.
正如你所看到的,进化是疯狂的,比 2012 年报告的速度快了大约560 倍。
On my machines and servers, following my numerous experiments, basics for loops are the fastest. This is even clearer using nested loops ($i $j $k..)
在我的机器和服务器上,经过我无数次的实验,循环的基础知识是最快的。使用嵌套循环($i $j $k..)
It is also the most flexible in usage, and has a better readability from my view.
它在使用上也是最灵活的,在我看来具有更好的可读性。