1. ホーム
  2. php

[解決済み] PHPにおけるFORとFOREACHの性能比較

2022-05-18 05:56:36

質問

まず最初に、90% のアプリケーションでは性能差はまったく関係ないことを理解していますが、どちらがより速い構成であるかを知る必要があります。それと...

現在、ネット上でそれらについて入手できる情報は混乱しています。多くの人が foreach は良くないと言いますが、技術的には、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

要するに

  • foreach よりも高速です。 foreach 参照で
  • foreach よりも高速です。 for
  • foreach よりも高速です。 for ハッシュテーブルの場合

誰か説明してください。

  1. 私は何か間違ったことをしているのでしょうか?
  2. PHPのforeachの参照は本当に違いがあるのでしょうか?つまり、なぜ参照渡しをするとコピーされないのでしょうか?
  3. ネット上でいくつか見かけましたが、それらをテストするたびにタイミングがずれてしまいます。また、いくつかの簡単なイテレータ構成をテストしましたが、まともな結果すら得られないようです。
  4. FOR/FOREACH (および WHILE) 以外に、配列を反復処理する高速な方法/メソッド/構造はありますか?

PHP バージョン 5.3.0


編集: 回答 ここの人々の助けで、私はすべての質問に対する答えをまとめることができました。ここでそれらを要約します。

  1. "私は何か間違ったことをしているのでしょうか。 ベンチマークで echo を使うことはできない、というのがコンセンサスのようです。個人的には、echo がランダムな実行時間を持つ関数であることや、他の関数がどう違うのかがまだわかりません。また、foreach の結果とまったく同じものを生成するスクリプトの能力は、単に echo" を使っている(私は何を使うべきだったのか)と説明するのは難しいものです。しかし、私はテストがより良い何かで行われるべきであることを認めます;しかし、理想的な妥協は思い浮かびません。
  2. PHP の foreach 参照というのは本当に違いがあるのでしょうか。つまり、参照渡しの場合、なぜコピーしないのでしょうか。 ircmaxellは、そうであることを示しています。さらなるテストにより、ほとんどの場合、参照の方が高速であることが証明されたようです -- 私の上記のコードの断片を考えると、ほとんどがすべてというわけではありませんが。私は、この問題はおそらくこのようなレベルで悩むにはあまりにも非直感的で、それぞれの状況に対してどちらが良いかを実際に判断するには、デコンパイルなどの極端なものが必要であることを認めます。
  3. ネット上でいくつか見かけましたが、テストするたびにタイミングがずれてしまいます。また、いくつかの簡単なイテレータをテストしましたが、まともな結果すら得られないようです。 ircmaxell が以下のような答えを出してくれました。ただし、このコードは PHP のバージョン >= 5 でしか有効ではないかもしれません。
  4. FOR/FOREACH (および WHILE) 以外に、配列を反復処理するための高速な方法/メソッド/構造はありますか? 回答してくれた Gordon に感謝します。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++) {

これは高価なループです。なぜなら、1回ごとの反復でカウントを呼び出すからです。そんなことをしない限りは、特に問題ないと思うのですが...。

参照の違いについては、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 では) 参照の方が著しく高速であるように見えるのです...。

EDITです。 PHP8.0を使用すると、以下のようになりました。

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

このテストは何度も繰り返され、ランキングの結果は一貫していました。