[php] PHP에서 FOR와 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++)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
참조 보다 빠릅니다foreach
보다 빠르다for
foreach
for
해시 테이블 보다 빠릅니다.
누군가 설명 할 수 있습니까?
- 내가 뭔가 잘못하고 있습니까?
- PHP foreach 참조가 실제로 차이를 일으키고 있습니까? 참조로 전달하면 왜 복사하지 않습니까?
- foreach 문에 해당하는 반복자 코드는 무엇입니까? 나는 인터넷에서 몇 가지를 보았지만 테스트 할 때마다 타이밍이 벗어났습니다. 또한 몇 가지 간단한 반복자 구문을 테스트했지만 괜찮은 결과를 얻지 못하는 것 같습니다. PHP의 배열 반복자는 끔찍한가요?
- FOR / FOREACH (및 WHILE) 이외의 배열을 반복하는 더 빠른 방법 / 방법 / 구성이 있습니까?
PHP 버전 5.3.0
편집 : 답변
여기 사람들의 도움으로 모든 질문에 대한 답변을 함께 작성할 수있었습니다. 여기에 요약하겠습니다 :
- “내가 뭔가 잘못하고 있니?” 합의는 다음과 같습니다 : 예, 벤치 마크에서 에코를 사용할 수 없습니다. 개인적으로, 나는 여전히 임의의 실행 시간을 가진 echo가 어떤 함수인지, 다른 함수가 어떻게 다른지 전혀 알지 못합니다-그 스크립트가 모든 것보다 foreach의 정확한 동일한 결과를 더 잘 생성하는 능력 그냥 “당신은 echo를 사용하고 있습니다”라고 설명합니다. 그러나 테스트가 더 나은 것으로 이루어져야한다고 생각합니다. 이상적인 타협은 생각 나지 않습니다.
- “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/
도와 주셔서 감사합니다.
간단한 순회에 대해 foreach (비 참조 버전)를 고수 할 것입니다.
답변
내 개인적인 의견은 상황에 맞는 것을 사용하는 것입니다. 개인적 for
으로 배열 탐색에는 거의 사용하지 않습니다 . 다른 유형의 반복에 사용하지만 foreach
너무 쉽습니다 … 대부분의 경우 시차가 최소화됩니다.
주의해야 할 것은 :
for ($i = 0; $i < count($array); $i++) {
매번 반복 할 때마다 카운트를 호출하기 때문에 비싼 루프입니다. 당신이 그렇게하지 않는 한, 나는 그것이 정말로 중요하다고 생각하지 않습니다 …
차이를 만드는 참조에 관해서는, 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에 있음).
답변
나는 이것이 놀랍다는 것을 확신하지 못한다. PHP로 코딩하는 대부분의 사람들은 PHP가 실제로 베어 메탈에서 실제로하는 일에 정통하지 않습니다. 나는 몇 가지를 언급 할 것이다.
-
변수를 수정하지 않으면 PHP에서 값이 빠릅니다. 어쨌든 참조가 계산되고 값을 기준으로 수행하면 덜 할 수 있기 때문입니다. ZVAL (대부분의 유형에 대한 HP의 내부 데이터 구조)을 두 번째로 수정하면 간단한 방식으로 분리해야합니다 (복사하고 다른 ZVAL은 잊어 버려야 함). 그러나 수정하지 않으므로 중요하지 않습니다. 참조 는 변수를 수정할 때 수행 할 작업을 알아야 하기 위해 더 많은 부기를 유지해야하기 때문에 복잡해집니다 . 따라서 읽기 전용 인 경우 역설적으로 &로 지적하는 것이 낫지 않습니다. 알아요, 그것은 직관적 인 카운터이지만 사실이기도합니다.
-
Foreach는 느리지 않습니다. 그리고 간단한 반복을 위해, 테스트중인 조건 ( “이 배열의 마지막에 있습니까?”)은 PHP opcode가 아닌 기본 코드를 사용하여 수행됩니다. APC 캐시 된 opcode 일지라도 베어 메탈에서 수행되는 많은 고유 작업보다 여전히 느립니다.
-
count () 때문에 for 루프 “for ($ i = 0; $ i <count ($ x); $ i ++) 사용이 느리고 구문 분석시 평가할 PHP 기능 (또는 실제로 해석 된 언어)이 부족합니다. 어떤 것이 든 배열을 수정하는지 여부로 인해 카운트를 한 번 평가할 수 없습니다.
-
그러나 “$ c = count ($ x);로 고치더라도 ($ i = 0; $ i <$ c; $ i ++) $ i <$ c는 최상의 젠드 opcode입니다. $ i ++ 10 만 번의 반복 과정에서 문제가 될 수 있습니다 Foreach는 무엇을해야 할지를 기본 수준에서 알고 있습니다. “이 배열의 끝에서 나는 오전”조건을 테스트하기 위해 PHP opcode가 필요하지 않습니다.
-
구식 “while (list (“물건)은 어떻습니까? 각 (), current () 등을 사용하면 적어도 하나의 함수 호출이 필요하지만 속도는 느리지 않지만 무료는 아닙니다. + list + 각각의 비용도 있습니다.
이러한 이유로 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
답변
2020 년이며 PHP 7.4 및 opcache로 물건이 크게 진화했습니다 .
다음은 echo 및 html 부분없이 unix CLI 로 실행 된 OP ^ 벤치 마크 입니다.
일반 컴퓨터에서 로컬로 테스트를 실행했습니다.
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 ..)
또한 사용이 가장 유연하며 내 견해에서 가독성이 좋습니다.
답변
나는 생각하지만 확실하지 않습니다 : for
루프는 값을 확인하고 증가시키기 위해 두 가지 작업을 수행합니다. foreach
메모리에 데이터를로드하면 모든 값을 반복합니다.