5

私はc++(MSVS)で少し速度テストを行っていましたが、奇妙な結果が得られました。1つのforループと複数のネストされたforループを使用する速度をテストしていました。コードは次のとおりです。

double testX = 0;
// Single loop executes in roughly 0.04 seconds
for( int i = 0; i < 27000000; i++ ){
    testX += 1;
}

// Nested loop executes in roughly 0.03 seconds
for( int x = 0; x < 300; x++ ){
    for( int y = 0; y < 300; y++ ){
        for( int z = 0; z < 300; z++ ){
            testX += 1;
        }
    }
}

ご覧のとおり、速度の違いはかなり明白です。私はこれを何度も実行しました、そしてそれらは私が見ている平均時間です(これらはglfwGetTime()を使用して計時されます)。

だから私の質問は:なぜですか?私の試験方法は不十分ですか?使用しているループが少なすぎませんか?私はグーグルを検索しようとしました、そして私が見つけた唯一の同様の質問は彼の問題をキャッシュコヒーレンシに関連させました、しかしこれらはループのために空であるため、私はそれが本当に効果があるとは思いませんでした。

どんな助けでも適用されます:)

編集:コメントのおかげで、空のforループを使用するのはおそらくテストの最良の方法ではないことに気づきました...そこで、コードを更新して、いくつかの(非常に)単純な操作をdoubleに実行しました。また、リリースモードでコンパイルしています。ただし、2つの方法は時間的にははるかに似ていますが、2番目の方法はまだわずかに高速です。

そして、はい、これはすべてのテストコードです(タイミング/出力関数を除いていますが、それらは質問に正確に固有ではありません)。

4

3 に答える 3

11

testX変数がコードの後半で使用された場合、コンパイラーはループを「最適化」しません。testXを出力するコードに1行追加するだけで、結果は次のようになります。

  • single for loop: 1.218 ms
  • nested for loop: 1.218 ms

これは、可能な場合はいつでも、コンパイラがネストされたループを単一のループに変換することを示しています。ループインデックスは、その最適化を防ぐために使用できます。

この方法でコードを変更する

for( int i = 0; i < 27000000; i++ ){
    testX += i;
}

for( int x = 0; x < 300; x++ ){
    testX += x;
    for( int y = 0; y < 300; y++ ){
        testX += y;
        for( int z = 0; z < 300; z++ ){
            testX += z;
        }
    }
}

ネストされたループに少しオーバーヘッドが追加されますが、実行時間は次のようになります

  • single for loop: 1.224 ms
  • nested for loop: 1.226 ms

ここに示されている時間は、30.000回のループ実行の平均です。

注:testX += x;90000分の1testX += x;のみが貢献し、300分の1のみが貢献します。したがって、上記の2つのセクションは引き続き比較可能です。

ネストされたループは単一のループよりもそれほど遅くはありませんが、それらがより速いというあなたの観察は真実ではありません

そして:あなたが示す時間は私が観察した時間の約40倍です。中速のハードウェアでテストを実行したので、コンパイラの設定を注意深く調べることをお勧めします。たぶんの結果glfwGetTime()は疑わしいです、そしてこれはあなたの質問の主な理由です。別のタイミングスキームを使用しようとしましたか?

編集:コンパイラの最適化を防ぐために、ループ制限を非定数に選択できます。

int lmt = rand() % 1 + 300;      // random value 300 or 301 
int big_lmt = lmt * lmt * lmt;   // random value 27000000 or 27270901

for( int i = 0; i < big_lmt; i++ ){
    testX += i;
}

for( int x = 0; x < lmt; x++ ){
    testX += x;
    for( int y = 0; y < lmt; y++ ){
        testX += y;
        for( int z = 0; z < lmt; z++ ){
            testX += z;
        }
    }
}

これにより、コンパイラの予測可能性が回避されます。

結果(lmt = 300比較可能なケースの場合):

  • single for loop: 1.213 ms
  • nested for loop: 1.216 ms

結果:

  • ネストされたループは、単一のループよりも高速ではありません。
于 2012-09-14T10:41:19.173 に答える
1

ループ内で変数(、、)を使用forxない場合、スマートコンパイラは、ネストせずに2番目の形式を単一のループに変換できます(変換する必要があります) 。このようなコンパイラの最適化を妨げない限り、ユーザーに実行時に、から、、またはストリームからの読み取りなどを入力させることにより、静的な予測可能性を削除します。yzforforxyzstdin

さらに、変数を使用して何かを行わない場合testX(たとえば、変数をに出力する場合stdout)、スマートコンパイラはそれを最適化できます(そしてそうすべきです)。つまり、デッドコードを完全に削除します。

ですから、私が言っているのは、現在のベンチマークはどういうわけか形が悪いということです。

于 2012-09-14T07:02:37.077 に答える
0

最善の策は、逆アセンブルを調べて、生成されたコードの違いを確認することです。コンパイラーはそこでかなり重い最適化を行っていると思います。

于 2012-09-14T06:52:39.907 に答える