PHPでのFORとFOREACHのパフォーマンス


134

まず、アプリケーションの90%でパフォーマンスの違いはまったく無関係であることを理解していますが、どちらがより高速な構成であるかを知る必要があります。それと...

現在ネット上で入手できる情報は混乱を招きます。多くの人がforeachは悪いと言いますが、反復子を使用して配列トラバーサルの記述を単純化することを想定しているため、技術的にはより高速であるはずです。イテレータ。これも高速であると思われますが、PHPでも明らかに非常に遅いです(または、これはPHPのものではありませんか?)。配列関数について話しています。next()、prev()、reset()など、関数のように見えるPHP言語機能の1つではない場合でも、

これを少し絞り込むと、配列を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. 「私は何か間違ったことをしていますか?」コンセンサスは次のようです:はい、ベンチマークでエコーを使用できません。個人的には、エコーが実行時間のランダムな関数であるか、他の関数がどういうわけかどのように異なるかはまだわかりません-それと、スクリプトがforeachのまったく同じ結果をすべてよりも優れて生成する機能は難しいです「あなたはエコーを使っている」とだけ説明します(まあ私は何を使っていたのでしょう)。しかし、私はテストがより良いもので行われるべきであると認めます。理想的な妥協は思い浮かびませんが。
  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、2010

1
foreach with referenceは、with with referenceに対して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を使用する必要があるコードの大きなチャンクです。@静止:私が聞いているのは基本的に「foreachでの参照は(常に)より速く、常に参照で書き込む」という行に沿っています。そのため、私はそのようにテストしました-他の参照ループとの比較にはあまり興味がありません
srcspider 2010

2
これらの空の質問は当然禁止されるべきです。それにphpbenchサイトをだましているサイト
あなたの常識

回答:


110

私の個人的な意見は、文脈で意味のあるものを使用することです。個人的に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にあります)。


1
「[計画外]最適化はすべての悪の根源」という意味ではありませんか?;)まあ、それらすべてが同じことをするので、それはそのままではそれほど最適化ではありません。また、未回答の質問もいくつかあります。コピーする必要がないためですが、参照の使用もオーバーヘッドではありませんか?私の質問でのスティルスタンディングのコメントは、あなたの仮定にも同意しないようです。また、なぜコードもそこで生成されるのが遅いのか。5.3.0でforeachが変更されて、array()がオブジェクト(SplFixedArrayなど)に変換されましたか?
srcspider 2010

@srcspider:参照を示すベンチマークコードと結果を含む編集された回答は、実際には非参照よりはるかに高速です...
ircmaxell

1
@srcspiderの"the better standard way to adopt." パフォーマンスは、採用するものを選択する唯一の基準ではありません。特にそのような遠方のケースでは。率直に言って、あなたはあなたの時間を浪費しているだけです
あなたの常識

@Col。爆弾私は100%同意します。この特定のケースでは、可読性と保守性がパフォーマンスを大幅に上回っています...標準を選択してそれに固執することに同意しますが、その標準を他の
もっと

@ircmaxell:スクリプトをすばやく実行することで要点が証明されたようですが、もう少し詳しく調べたいと思います。新しい5.3の機能のいくつかを含めるために、より多くのテストで元の質問を編集する場合があります。@Col。破片:FORはほぼユニバーサルなプログラミング-幼稚園レベルであり、FOREACHはより単純な構文です。読みやすさに関しては、同じように見えます。これはすべて低レベルなので、高レベルのパターンのようにメンテナンスが問題になるとは思いません。そして、この「基本的な構成」は、私が書く多くのコードを説明するので、時間を浪費しているとは思いません。:)
srcspider 2010

54

これが驚くべきことかどうかはわかりません。PHPでコーディングするほとんどの人は、PHPが実際にベアメタルで行っていることをよく理解していません。私はいくつかのことを述べますが、ほとんどの場合そうです。

  1. 変数を変更しない場合、PHPでは値渡しの方が高速です。これは、参照がとにかくカウントされており、値渡しのほうが少ないためです。ZVAL(ほとんどのタイプのPHPの内部データ構造)を2番目に変更することがわかっているので、簡単に(コピーして他のZVALを忘れて)中断する必要があります。ただし、変更することはないため、問題ではありません。参照を使用すると、変数を変更するときに何をべきかを知るために行う必要のある簿記が増えるため、さらに複雑になります。したがって、読み取り専用の場合は、逆説的に言えば、&を使用することが重要ではありません。私は知っている、それは直感に反するものですが、それは本当です。

  2. Foreachは遅くありません。そして単純な反復の場合、テスト対象の条件(「この配列の最後にいる」)は、PHPのオペコードではなくネイティブコードを使用して行われます。APCでキャッシュされたオペコードであっても、ベアメタルで実行される一連のネイティブ操作よりも低速です。

  3. forループ "for($ i = 0; $ i <count($ x); $ i ++)の使用は、count()と、解析時に評価するPHPの機能(または実際に解釈された言語)がないために遅くなります何かが配列を変更するかどうかの時間これは、配列がカウントを一度評価するのを防ぎます。

  4. しかし、 "$ c = count($ x);で修正しても、($ i = 0; $ i <$ c; $ i ++)の場合、$ i <$ cは、Zendオペコードの集まりです。 $ i ++。100,000回の反復の過程で、これは問題になる可能性があります。Foreachはネイティブレベルで何をすべきかを知っています。「この配列の最後にIがある」条件をテストするためのPHPオペコードは必要ありません。

  5. 古い学校の "while(list(" stuff?)についてはどうでしょうか?まあ、each()やcurrent()などを使用すると、少なくとも1つの関数呼び出しが必要になりますが、遅くはありませんが無料ではありません。再びPHPオペコードです!つまり、リスト+にはそれぞれコストもかかります。

これらの理由から、foreachは当然のことながら単純な反復の最良のオプションです。

忘れないでください。読むのも最も簡単なので、双方にメリットがあります。


これはまさに私が探していた説明です、ありがとう。
2015

この回答は、実際にはマークされた回答の補足または要約である必要があります。よく読んでよかった。
doz87 2016年

30

ベンチマーク(特にphpbench.com)で注意すべき点の1つは、数値は確かですが、テストはそうではないということです。些細であり、実際にそれをテストしないスキューベンチマークや配列の繰り返し処理の場合、キャッシュ配列の検索にPHPの能力を乱用でphpbench.comのテストの多くは、物事をやっている現実世界の例(誰も書き込みがために空にしませんループ)。私が見つけた私自身のベンチマークを実行しましたが、実際の結果をかなり反映しており、それらは常に言語のネイティブ反復構文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で大きく進化しました

これは、エコーおよび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ループがチェックし、値をインクリメントするために2つの操作を要します。foreachメモリにデータをロードすると、すべての値が反復されます。


7
誰もが意見を持っていて、人々は答えを見つけるためにStack Overflowに行きます。あなたは必ず何状態のない場合は、マニュアルを参照してソースコードを確認するなど、Google検索を行う
セバスチャン・F.

パフォーマンスは研究とテストに基づいているため、いくつかの証拠が必要です。それに応じて参照を提供してください。うまくいけば、回答を改善できます。
Marwan Salim、2018

サーバーの実際の負荷とループで何をしたいかにも依存すると思います。サーバーの実際の負荷とループで何をしたいかにも依存すると思います。番号付き配列を反復する場合は、foreachまたはforループを使用する方がよいかどうかを知りたいため、PHP 7.4 でsandbox.onlinephpfunctions.comのベンチマークを実行しました。同じスクリプトを何度も繰り返し実行すると、実行するたびに異なる結果が得られます。ある場合はforループの方が高速で、別の場合はforeachループで同等でした。
Alexander Behling
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.