8

次のようなループがあるとします。

for(int i = 0; i < 10000; i++) {
    /* Do something computationally expensive */
    if (i < 200 && !(i%20)) {
        /* Do something else */
    }
}

いくつかの些細なタスクが、数回しか実行されない if ステートメントの背後でスタックします。「ループ中のif文は遅い!」とよく耳にします。したがって、(わずかに) パフォーマンスが向上することを期待して、ループを次のように分割します。

for(int i = 0; i < 200; i++) {
    /* Do something computationally expensive */
    if (!(i%20)) {
        /* Do something else */
    }
}

for(int i = 200; i < 10000; i++) {
    /* Do something computationally expensive */
}

gcc (-O3 などの適切なフラグを使用) は、1 つのループを自動的に 2 つに分割しますか? それとも展開して反復回数を減らすだけですか?

4

1 に答える 1

11

プログラムを逆アセンブルして、自分の目で確かめてみませんか? しかし、ここに行きます。これはテストプログラムです:

int main() {
    int sum = 0;
    int i;
    for(i = 0; i < 10000; i++) {
        if (i < 200 && !(i%20)) {
            sum += 0xC0DE;
        }
        sum += 0xCAFE;
    }
    printf("%d\n", sum);
    return 0;
}

これは、gcc 4.3.3 と -o3 でコンパイルされた逆アセンブル コードの興味深い部分です。

0x08048404 <main+20>:   xor    ebx,ebx
0x08048406 <main+22>:   push   ecx
0x08048407 <main+23>:   xor    ecx,ecx
0x08048409 <main+25>:   sub    esp,0xc
0x0804840c <main+28>:   lea    esi,[esi+eiz*1+0x0]
0x08048410 <main+32>:   cmp    ecx,0xc7
0x08048416 <main+38>:   jg     0x8048436 <main+70>
0x08048418 <main+40>:   mov    eax,ecx
0x0804841a <main+42>:   imul   esi
0x0804841c <main+44>:   mov    eax,ecx
0x0804841e <main+46>:   sar    eax,0x1f
0x08048421 <main+49>:   sar    edx,0x3
0x08048424 <main+52>:   sub    edx,eax
0x08048426 <main+54>:   lea    edx,[edx+edx*4]
0x08048429 <main+57>:   shl    edx,0x2
0x0804842c <main+60>:   cmp    ecx,edx
0x0804842e <main+62>:   jne    0x8048436 <main+70>
0x08048430 <main+64>:   add    ebx,0xc0de
0x08048436 <main+70>:   add    ecx,0x1
0x08048439 <main+73>:   add    ebx,0xcafe
0x0804843f <main+79>:   cmp    ecx,0x2710
0x08048445 <main+85>:   jne    0x8048410 <main+32>
0x08048447 <main+87>:   mov    DWORD PTR [esp+0x8],ebx
0x0804844b <main+91>:   mov    DWORD PTR [esp+0x4],0x8048530
0x08048453 <main+99>:   mov    DWORD PTR [esp],0x1
0x0804845a <main+106>:  call   0x8048308 <__printf_chk@plt>

ご覧のとおり、この特定の例では、そうではありません。main+32 で開始し、main+85 で終了するループは 1 つだけです。アセンブリ コードの読み取りに問題がある場合は、ecx = i; ebx = 合計。

しかし、それでもマイレージは異なる場合があります-この特定のケースでどのヒューリスティックが使用されるかは誰にもわかりません。そのため、念頭に置いたコードをコンパイルし、より長く/より複雑な計算がオプティマイザーにどのように影響するかを確認する必要があります.

ただし、最新の CPU では、分岐予測子はこのような簡単なコードで十分に機能するため、どちらの場合もパフォーマンスの低下はあまり見られません。計算量の多いコードが数十億サイクルを必要とする場合、わずかな予測ミスでパフォーマンスがどの程度低下するでしょうか?

于 2011-02-16T15:20:55.483 に答える