PHP에서 FOR와 FOREACH의 성능


134

우선, 응용 프로그램의 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

한마디로 :

  • foreachforeach참조 보다 빠릅니다
  • foreach 보다 빠르다 for
  • foreachfor해시 테이블 보다 빠릅니다.

누군가 설명 할 수 있습니까?

  1. 내가 뭔가 잘못하고 있습니까?
  2. PHP foreach 참조가 실제로 차이를 일으키고 있습니까? 참조로 전달하면 왜 복사하지 않습니까?
  3. foreach 문에 해당하는 반복자 코드는 무엇입니까? 나는 인터넷에서 몇 가지를 보았지만 테스트 할 때마다 타이밍이 벗어났습니다. 또한 몇 가지 간단한 반복자 구문을 테스트했지만 괜찮은 결과를 얻지 못하는 것 같습니다. PHP의 배열 반복자는 끔찍한가요?
  4. FOR / FOREACH (및 WHILE) 이외의 배열을 반복하는 더 빠른 방법 / 방법 / 구성이 있습니까?

PHP 버전 5.3.0


편집 : 답변 여기 사람들의 도움으로 모든 질문에 대한 답변을 함께 작성할 수있었습니다. 여기에 요약하겠습니다 :

  1. "내가 뭔가 잘못하고 있니?" 합의는 다음과 같습니다 : 예, 벤치 마크에서 에코를 사용할 수 없습니다. 개인적으로, 나는 여전히 임의의 실행 시간을 가진 echo가 어떤 함수인지, 다른 함수가 어떻게 다른지 전혀 알지 못합니다-그 스크립트가 모든 것보다 foreach의 정확한 동일한 결과를 더 잘 생성하는 능력 그냥 "당신은 echo를 사용하고 있습니다"라고 설명합니다. 그러나 테스트가 더 나은 것으로 이루어져야한다고 생각합니다. 이상적인 타협은 생각 나지 않습니다.
  2. "PHP foreach 참조가 실제로 차이를 만들어 내고 있습니까? 참조로 전달하면 왜 복사하지 않습니까?" ircmaxell은 그렇습니다. 추가 테스트를 통해 대부분의 경우 참조가 더 빨라야한다는 것을 알 수 있습니다. 위의 코드 조각을 감안할 때 가장 의미있는 것은 아닙니다. 나는이 문제가 그러한 수준에서 귀찮게하기에는 너무 직관적이지 않을 수 있으며 실제로 각 상황에 어떤 것이 더 좋은지를 결정하기 위해 디 컴파일과 같은 극단적 인 것이 필요하다는 것을 인정합니다.
  3. "foreach 문의 동등한 반복자 코드는 무엇입니까? 인터넷에서 몇 가지를 보았지만 테스트 할 때마다 타이밍이 나갔습니다. 또한 간단한 반복자 구성도 테스트했지만 괜찮은 결과를 얻지 못한 것 같습니다. -PHP에서 배열 반복자가 끔찍합니까? " ircmaxell은 다음과 같이 대답했습니다. 코드는 PHP 버전> = 5에만 유효 할 수 있지만
  4. "FOR / FOREACH (및 WHILE) 이외의 배열을 반복하는 더 빠른 방법 / 방법 / 구성이 있습니까?" 고든에게 대답 해 주셔서 감사합니다. PHP5에서 새로운 데이터 유형을 사용하면 성능이 향상되거나 메모리가 향상됩니다 (상황에 따라 바람직 할 수 있음). 속도가 현명한 많은 새로운 유형의 배열이 array ()보다 낫지는 않지만 splpriorityqueue 및 splobjectstorage는 실질적으로 더 빠른 것 같습니다. Gordon이 제공 한 링크 : http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/

도와 주셔서 감사합니다.

간단한 순회에 대해 foreach (비 참조 버전)를 고수 할 것입니다.


7
벤치마킹 규칙 2.71 : 벤치마킹하지 마십시오.
Mchl

1
참조를 가진 foreach는 참조를 위해 bechmarked해야합니다. 거기에 결점이 있습니다. do-while 루프에서도 참조를 사용하지 않으면 참조가없는 것보다 속도가 느려질 것입니다.
bcosca

2
이것은 php 5.3을위한 것이기 때문에, 새로운 Spl 데이터 타입 대 배열 테스트를 고려할 수도 있습니다. 또는 여기를보십시오 : matthewturland.com/2010/05/20/new-spl-features-in-php-5-3
Gordon

@ Mchl : 몇 번 실행했는데 같은 결과를 얻었습니다. 에코가 벤치 마크를 손상시키는 경우 완전히 임의의 결과를 얻지 않아야합니까? 또한 나는 무언가를 반복하고 출력하기를 원하므로 에코가 실제로 나에게 정말로 중요합니다. 에코 할 때 foreach가 더 빠르면 foreach를 사용해야하는 큰 코드 덩어리입니다. @ stillstanding : 내가 듣고있는 것은 기본적으로 "foreach의 참조가 항상 빠릅니다 (항상), 항상 참조로 작성하십시오"라는 선을 따릅니다. 그래서 나는 그렇게 테스트 한 이유입니다.
srcspider

2
이 빈 질문은 자연스럽게 금지되어야합니다. 뿐만 아니라 속이는 phpbench 사이트
귀하의 상식

답변:


110

내 개인적인 의견은 상황에 맞는 것을 사용하는 것입니다. 개인적 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에 있음).


1
"[계획되지 않음] 최적화가 모든 악의 근원"이라는 의미가 아닙니까? ;) 글쎄, 그들 모두가 같은 일을하는 것입니다. 따라서 "최적의 표준 채택 방법"인 것만 큼 최적화 된 것은 아닙니다. 또한 더 많은 질문에 대답하지 않았습니다. 복사 할 필요가 없기 때문에 말하지만 참조 사용도 오버 헤드가 아닌가? 내 질문에 대한 stillstanding의 의견은 귀하의 가정에도 동의하지 않는 것 같습니다. 또한 왜 코드가 더 느리게 참조 되는가? 5.3.0에서 foreach를 변경하여 array ()를 객체 (예 : SplFixedArray)로 변환 했습니까?
srcspider

@srcspider : 벤치 마크 코드로 편집 된 답변과 참조를 보여주는 결과는 실제로 비 참조보다 훨씬 빠릅니다 ...
ircmaxell

1
@srcspider "the better standard way to adopt." 성능은 채택 대상을 선택하는 유일한 기준은 아닙니다. 특히 파 페치 된 경우에. 솔직히, 당신은 당신의 시간을 낭비하고 있습니다
당신의 상식

@안부. 파편 나는 100 % 동의합니다. 가독성과 유지 보수성은이 특별한 경우에 성능을 크게 떨어 뜨립니다. 나는 표준을 고르고 그 표준을 고수하는 데 동의하지만 그 표준을 다른 더 중요한 요소들에 근거합니다.
ircmaxell

@ircmaxell : 빠르게 스크립트를 실행하면 요점을 입증하는 것 같지만 조금 더 자세히 살펴보고 싶습니다. 새로운 5.3 기능 중 일부를 포함하기 위해 더 많은 테스트로 원래 질문을 편집 할 수 있습니다. @안부. Shrapnel : FOR는 거의 보편적 인 프로그래밍-유치원 수준이며 FOREACH는 더 간단한 구문입니다. 가독성까지는 평등 한 것 같습니다. 이것은 매우 낮은 수준이므로 유지 보수가 일부 높은 수준의 패턴과 같은 문제라고 생각하지 않습니다. 그리고이 "기본 구조"가 내가 작성할 많은 코드를 설명하기 때문에 시간을 낭비하고 있다고 생각하지 않습니다. :)
srcspider

54

나는 이것이 놀랍다는 것을 확신하지 못한다. PHP로 코딩하는 대부분의 사람들은 PHP가 실제로 베어 메탈에서 실제로하는 일에 정통하지 않습니다. 나는 몇 가지를 언급 ​​할 것이다.

  1. 변수를 수정하지 않으면 PHP에서 값이 빠릅니다. 어쨌든 참조가 계산되고 값을 기준으로 수행하면 덜 할 수 있기 때문입니다. ZVAL (대부분의 유형에 대한 HP의 내부 데이터 구조)을 두 번째로 수정하면 간단한 방식으로 분리해야합니다 (복사하고 다른 ZVAL은 잊어 버려야 함). 그러나 수정하지 않으므로 중요하지 않습니다. 참조 는 변수를 수정할 때 수행 할 작업을 알아야 하기 위해 더 많은 부기를 유지해야하기 때문에 복잡해집니다 . 따라서 읽기 전용 인 경우 역설적으로 &로 지적하는 것이 낫지 않습니다. 알아요, 그것은 직관적 인 카운터이지만 사실이기도합니다.

  2. Foreach는 느리지 않습니다. 그리고 간단한 반복을 위해, 테스트중인 조건 ( "이 배열의 마지막에 있습니까?")은 PHP opcode가 아닌 기본 코드를 사용하여 수행됩니다. APC 캐시 된 opcode 일지라도 베어 메탈에서 수행되는 많은 고유 작업보다 여전히 느립니다.

  3. count () 때문에 for 루프 "for ($ i = 0; $ i <count ($ x); $ i ++) 사용이 느리고 구문 분석시 평가할 PHP 기능 (또는 실제로 해석 된 언어)이 부족합니다. 어떤 것이 든 배열을 수정하는지 여부로 인해 카운트를 한 번 평가할 수 없습니다.

  4. 그러나 "$ c = count ($ x);로 고치더라도 ($ i = 0; $ i <$ c; $ i ++) $ i <$ c는 최상의 젠드 opcode입니다. $ i ++ 10 만 번의 반복 과정에서 문제가 될 수 있습니다 Foreach는 무엇을해야 할지를 기본 수준에서 알고 있습니다. "이 배열의 끝에서 나는 오전"조건을 테스트하기 위해 PHP opcode가 필요하지 않습니다.

  5. 구식 "while (list ("물건)은 어떻습니까? 각 (), current () 등을 사용하면 적어도 하나의 함수 호출이 필요하지만 속도는 느리지 않지만 무료는 아닙니다. + list + 각각의 비용도 있습니다.

이러한 이유로 foreach는 간단한 반복에 가장 적합한 옵션입니다.

잊지 말고 읽는 것도 가장 쉬운 방법이므로 상생입니다.


이것은 내가 찾던 정확한 설명입니다. 감사합니다.
hardsetting

이 답변은 실제로 표시된 답변에 대한 보충 또는 요약이어야합니다. 잘 읽어서 다행입니다.
doz87

30

벤치 마크 (특히 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

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 ..)

또한 사용이 가장 유연하며 내 견해에서 가독성이 좋습니다.


0

나는 생각하지만 확실하지 않습니다 : for루프는 값을 확인하고 증가시키기 위해 두 가지 작업을 수행합니다. foreach메모리에 데이터를로드하면 모든 값을 반복합니다.


7
모두 의견이 있고 사람들은 Stack Overflow에 와서 답을 찾습니다. 자신의 상태를 잘 모르는 경우 소스 코드, 설명서를 확인하고 Google 검색 등을 수행하십시오.
Sebastien F.

성능은 연구 및 테스트를 기반으로하기 때문에 몇 가지 증거를 제시해야합니다. 그에 따라 참조를 제공하십시오. 희망적으로 당신은 당신의 대답을 향상시킬 수 있습니다.
Marwan Salim

서버의 실제 부하와 루프에서 수행하려는 작업에 따라 달라집니다. 서버의 실제 부하와 루프에서 수행하려는 작업에 따라 달라집니다. 번호가 매겨진 배열을 반복하는 것이 foreach 또는 for-loop를 사용 해야하는지 알고 싶었으므로 sandbox.onlinephpfunctions.com 에서 PHP 7.4로 벤치 마크를 실행했습니다 . 동일한 스크립트를 여러 번 반복해서 실행하면 매번 다른 결과가 나옵니다. 한 번은 for-loop가 다른 시간은 foreach-loop와 다른 시간이 더 빨랐습니다.
Alexander Behling
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.