web-dev-qa-db-ja.com

「切り替え」は「if」より高速ですか?

switchステートメントは実際にはifステートメントよりも高速ですか?

/Oxフラグを使用して、Visual Studio 2010のx64 C++コンパイラで次のコードを実行しました。

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define MAX_COUNT (1 << 29)
size_t counter = 0;

size_t testSwitch()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        switch (counter % 4 + 1)
        {
            case 1: counter += 4; break;
            case 2: counter += 3; break;
            case 3: counter += 2; break;
            case 4: counter += 1; break;
        }
    }
    return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}

size_t testIf()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = counter % 4 + 1;
        if (c == 1) { counter += 4; }
        else if (c == 2) { counter += 3; }
        else if (c == 3) { counter += 2; }
        else if (c == 4) { counter += 1; }
    }
    return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}

int main()
{
    printf("Starting...\n");
    printf("Switch statement: %u ms\n", testSwitch());
    printf("If     statement: %u ms\n", testIf());
}

そして、これらの結果を得ました:

Switchステートメント:5261ミリ秒
Ifステートメント:5196ミリ秒

私が学んだことから、switchステートメントは明らかにジャンプテーブルを使用して分岐を最適化します。

質問:

  1. X86またはx64では、基本的なジャンプテーブルはどのように見えますか?

  2. このコードはジャンプテーブルを使用していますか?

  3. この例にパフォーマンスの違いがないのはなぜですか? にパフォーマンスの大きな違いがある状況はありますか?


コードの分解:

testIf:

13FE81B10 sub  rsp,48h 
13FE81B14 call qword ptr [__imp_clock (13FE81128h)] 
13FE81B1A mov  dword ptr [start],eax 
13FE81B1E mov  qword ptr [i],0 
13FE81B27 jmp  testIf+26h (13FE81B36h) 
13FE81B29 mov  rax,qword ptr [i] 
13FE81B2E inc  rax  
13FE81B31 mov  qword ptr [i],rax 
13FE81B36 cmp  qword ptr [i],20000000h 
13FE81B3F jae  testIf+0C3h (13FE81BD3h) 
13FE81B45 xor  edx,edx 
13FE81B47 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B4E mov  ecx,4 
13FE81B53 div  rax,rcx 
13FE81B56 mov  rax,rdx 
13FE81B59 inc  rax  
13FE81B5C mov  qword ptr [c],rax 
13FE81B61 cmp  qword ptr [c],1 
13FE81B67 jne  testIf+6Dh (13FE81B7Dh) 
13FE81B69 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B70 add  rax,4 
13FE81B74 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81B7B jmp  testIf+0BEh (13FE81BCEh) 
13FE81B7D cmp  qword ptr [c],2 
13FE81B83 jne  testIf+89h (13FE81B99h) 
13FE81B85 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B8C add  rax,3 
13FE81B90 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81B97 jmp  testIf+0BEh (13FE81BCEh) 
13FE81B99 cmp  qword ptr [c],3 
13FE81B9F jne  testIf+0A5h (13FE81BB5h) 
13FE81BA1 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81BA8 add  rax,2 
13FE81BAC mov  qword ptr [counter (13FE835D0h)],rax 
13FE81BB3 jmp  testIf+0BEh (13FE81BCEh) 
13FE81BB5 cmp  qword ptr [c],4 
13FE81BBB jne  testIf+0BEh (13FE81BCEh) 
13FE81BBD mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81BC4 inc  rax  
13FE81BC7 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81BCE jmp  testIf+19h (13FE81B29h) 
13FE81BD3 call qword ptr [__imp_clock (13FE81128h)] 
13FE81BD9 sub  eax,dword ptr [start] 
13FE81BDD imul eax,eax,3E8h 
13FE81BE3 cdq       
13FE81BE4 mov  ecx,3E8h 
13FE81BE9 idiv eax,ecx 
13FE81BEB cdqe      
13FE81BED add  rsp,48h 
13FE81BF1 ret       

testSwitch:

13FE81C00 sub  rsp,48h 
13FE81C04 call qword ptr [__imp_clock (13FE81128h)] 
13FE81C0A mov  dword ptr [start],eax 
13FE81C0E mov  qword ptr [i],0 
13FE81C17 jmp  testSwitch+26h (13FE81C26h) 
13FE81C19 mov  rax,qword ptr [i] 
13FE81C1E inc  rax  
13FE81C21 mov  qword ptr [i],rax 
13FE81C26 cmp  qword ptr [i],20000000h 
13FE81C2F jae  testSwitch+0C5h (13FE81CC5h) 
13FE81C35 xor  edx,edx 
13FE81C37 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C3E mov  ecx,4 
13FE81C43 div  rax,rcx 
13FE81C46 mov  rax,rdx 
13FE81C49 inc  rax  
13FE81C4C mov  qword ptr [rsp+30h],rax 
13FE81C51 cmp  qword ptr [rsp+30h],1 
13FE81C57 je   testSwitch+73h (13FE81C73h) 
13FE81C59 cmp  qword ptr [rsp+30h],2 
13FE81C5F je   testSwitch+87h (13FE81C87h) 
13FE81C61 cmp  qword ptr [rsp+30h],3 
13FE81C67 je   testSwitch+9Bh (13FE81C9Bh) 
13FE81C69 cmp  qword ptr [rsp+30h],4 
13FE81C6F je   testSwitch+0AFh (13FE81CAFh) 
13FE81C71 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C73 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C7A add  rax,4 
13FE81C7E mov  qword ptr [counter (13FE835D0h)],rax 
13FE81C85 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C87 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C8E add  rax,3 
13FE81C92 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81C99 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C9B mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81CA2 add  rax,2 
13FE81CA6 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81CAD jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81CAF mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81CB6 inc  rax  
13FE81CB9 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81CC0 jmp  testSwitch+19h (13FE81C19h) 
13FE81CC5 call qword ptr [__imp_clock (13FE81128h)] 
13FE81CCB sub  eax,dword ptr [start] 
13FE81CCF imul eax,eax,3E8h 
13FE81CD5 cdq       
13FE81CD6 mov  ecx,3E8h 
13FE81CDB idiv eax,ecx 
13FE81CDD cdqe      
13FE81CDF add  rsp,48h 
13FE81CE3 ret       

更新:

興味深い結果 ここ 。ただし、なぜ1つが速く、1つが遅いのかはわかりません。

232
Mehrdad

コンパイラーcanがスイッチに対して行う最適化がいくつかあります。よく言われる「ジャンプテーブル」は、入力を何らかの方法で制限できる場合にのみ機能するため、非常に便利なものではないと思います。

「ジャンプテーブル」のC疑似コードは this のようなものになります。実際には、コンパイラはテーブルをテストする何らかの形式を挿入して、テーブルで入力が有効であることを確認する必要があります。 。また、入力が連続した数字の連続であるという特定の場合にのみ機能することにも注意してください。

スイッチ内のブランチの数が非常に多い場合、コンパイラはスイッチの値でバイナリ検索を使用するなどのことを実行できます。シナリオは、スイッチと同じくらい一般的であり、生成されるコードサイズが大きくなることはありません。しかし、それを確認するには、違いを確認するためにテストコードにさらに多くのブランチが必要です。

特定の質問に答えるには:

  1. Clangは this のようなものを生成します:

    test_switch(char):                       # @test_switch(char)
            movl    %edi, %eax
            cmpl    $19, %edi
            jbe     .LBB0_1
            retq
    .LBB0_1:
            jmpq    *.LJTI0_0(,%rax,8)
            jmp     void call<0u>()         # TAILCALL
            jmp     void call<1u>()         # TAILCALL
            jmp     void call<2u>()         # TAILCALL
            jmp     void call<3u>()         # TAILCALL
            jmp     void call<4u>()         # TAILCALL
            jmp     void call<5u>()         # TAILCALL
            jmp     void call<6u>()         # TAILCALL
            jmp     void call<7u>()         # TAILCALL
            jmp     void call<8u>()         # TAILCALL
            jmp     void call<9u>()         # TAILCALL
            jmp     void call<10u>()        # TAILCALL
            jmp     void call<11u>()        # TAILCALL
            jmp     void call<12u>()        # TAILCALL
            jmp     void call<13u>()        # TAILCALL
            jmp     void call<14u>()        # TAILCALL
            jmp     void call<15u>()        # TAILCALL
            jmp     void call<16u>()        # TAILCALL
            jmp     void call<17u>()        # TAILCALL
            jmp     void call<18u>()        # TAILCALL
            jmp     void call<19u>()        # TAILCALL
    .LJTI0_0:
            .quad   .LBB0_2
            .quad   .LBB0_3
            .quad   .LBB0_4
            .quad   .LBB0_5
            .quad   .LBB0_6
            .quad   .LBB0_7
            .quad   .LBB0_8
            .quad   .LBB0_9
            .quad   .LBB0_10
            .quad   .LBB0_11
            .quad   .LBB0_12
            .quad   .LBB0_13
            .quad   .LBB0_14
            .quad   .LBB0_15
            .quad   .LBB0_16
            .quad   .LBB0_17
            .quad   .LBB0_18
            .quad   .LBB0_19
            .quad   .LBB0_20
            .quad   .LBB0_21
    
  2. 私はそれがジャンプテーブルを使用していないと言うことができます-4つの比較指示がはっきりと見えます:

    13FE81C51 cmp  qword ptr [rsp+30h],1 
    13FE81C57 je   testSwitch+73h (13FE81C73h) 
    13FE81C59 cmp  qword ptr [rsp+30h],2 
    13FE81C5F je   testSwitch+87h (13FE81C87h) 
    13FE81C61 cmp  qword ptr [rsp+30h],3 
    13FE81C67 je   testSwitch+9Bh (13FE81C9Bh) 
    13FE81C69 cmp  qword ptr [rsp+30h],4 
    13FE81C6F je   testSwitch+0AFh (13FE81CAFh) 
    

    ジャンプテーブルベースのソリューションでは、比較はまったく使用されません。

  3. コンパイラがジャンプテーブルを生成するのに十分なブランチがないか、コンパイラが単にジャンプテーブルを生成しません。どっちかわからない。

EDIT 2014:LLVMオプティマイザーに精通している人々から、ジャンプテーブルの最適化が多くのシナリオで重要になる可能性があるとの議論がありました。例えば多くの値を持つ列挙があり、その列挙の値に対する多くのケースがある場合。そうは言っても、私は2011年に私が上記のことを支持します-「私がそれを切り替えれば、どんなに多くのケースがあったとしても同じ時間になるだろう」と人々が思うことはよくあります-それは完全に間違っています。ジャンプテーブルを使用しても、間接ジャンプコストが発生し、各ケースのテーブルのエントリに対して支払います。また、メモリ帯域幅は、最新のハードウェアにとって大きな問題です。

読みやすくするためにコードを書きます。 ソルトに値するコンパイラーは、if/else ifラダーを確認し、同等のスイッチに変換するか、高速化する場合はその逆になります

117
Billy ONeal

あなたの質問に:

1。x86またはx64では、基本的なジャンプテーブルはどのように見えますか?

ジャンプテーブルは、配列構造などのラベルへのポインタを保持するメモリアドレスです。次の例は、ジャンプテーブルのレイアウト方法を理解するのに役立ちます。

00B14538  D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00  Ø.«.Ø.«.Ø.«.Ø.«.
00B14548  D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 00 00 00 00  Ø.«.Ø.«.Ø.«.....
00B14558  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00B14568  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

enter image description here

ここで、00B14538はジャンプテーブルへのポインターであり、D8 09 AB 00などの値はラベルポインターを表します。

2。このコードはジャンプテーブルを使用していますか?この場合はありません。

3。この例にパフォーマンスの違いがないのはなぜですか?

両方のケースの命令は同じに見え、ジャンプテーブルがないため、パフォーマンスの違いはありません。

4。パフォーマンスに大きな違いがある状況はありますか?

ifチェックの非常に長いシーケンスがある場合、その場合、ジャンプテーブルを使用するとパフォーマンスが向上します(分岐/ jmp命令はexpensive)ほぼ完全に予測できない場合)、メモリのコストが伴います。

すべての比較命令のコードもサイズがあるため、特に32ビットポインターまたはオフセットでは、1回のジャンプテーブルルックアップで実行可能ファイルのサイズが大きくなることはありません。

結論:コンパイラーはそのようなケースを処理し、適切な命令を生成するのに十分なスマートです:)

43
crypted

コンパイラーは、switchステートメントをifステートメントと同等のコードとして自由にコンパイルしたり、ジャンプテーブルを作成したりできます。コンパイラオプションで指定した内容に応じて、最速で実行されるものまたは最小のコードを生成するものに基づいて、どちらか一方を選択する可能性があります。したがって、最悪の場合、ifステートメントと同じ速度になります

コンパイラが最善の選択を行い、コードを最も読みやすくするものに焦点を当てると信頼しています。

ケースの数が非常に多くなると、ジャンプテーブルは一連のifよりもはるかに高速になります。ただし、値間のステップが非常に大きい場合、ジャンプテーブルが大きくなる可能性があり、コンパイラはジャンプテーブルを生成しないことを選択する場合があります。

31
Soren

スイッチテストループ中にテストに関係のないタスクをコンピューターが実行しておらず、ifテストループ中に実行しているタスクが少ないことをどのように確認しますか?テスト結果には、次のものは何も表示されません。

  1. 差は非常に小さい
  2. 結果は1つだけであり、一連の結果はありません
  3. ケースが少なすぎる

私の結果:

私は付け加えた:

printf("counter: %u\n", counter);

あなたの例ではカウンタが使用されなかったため、ループを最適化しないように最後まで実行しましたが、なぜコンパイラはループを実行しますか?すぐに、そのようなマイクロベンチマークがあっても、スイッチは常に勝っていました。

コードのその他の問題は次のとおりです。

switch (counter % 4 + 1)

あなたのスイッチループで、対

const size_t c = counter % 4 + 1; 

ifループ内。それを修正する場合の非常に大きな違い。 switchステートメント内にステートメントを配置すると、コンパイラーは値を最初にスタックに配置するのではなく、CPUレジスタに直接送信するようになります。したがって、これはバランスの取れたテストではなく、switchステートメントに有利です。

ああ、私もあなたがテストの間にカウンターをリセットするべきだと思います。実際、おそらく何かを最適化するため、+ 1、+ 2、+ 3などの代わりに何らかの乱数を使用する必要があります。乱数とは、たとえば現在の時刻に基づいた数値を意味します。そうしないと、コンパイラーは両方の関数を1つの長い数学演算に変換し、ループに煩わされることさえありません。

ライアンのコードを修正して、コードが実行される前にコンパイラーが問題を見つけられないようにしました。

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define MAX_COUNT (1 << 26)
size_t counter = 0;

long long testSwitch()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = Rand() % 20 + 1;

        switch (c)
        {
                case 1: counter += 20; break;
                case 2: counter += 33; break;
                case 3: counter += 62; break;
                case 4: counter += 15; break;
                case 5: counter += 416; break;
                case 6: counter += 3545; break;
                case 7: counter += 23; break;
                case 8: counter += 81; break;
                case 9: counter += 256; break;
                case 10: counter += 15865; break;
                case 11: counter += 3234; break;
                case 12: counter += 22345; break;
                case 13: counter += 1242; break;
                case 14: counter += 12341; break;
                case 15: counter += 41; break;
                case 16: counter += 34321; break;
                case 17: counter += 232; break;
                case 18: counter += 144231; break;
                case 19: counter += 32; break;
                case 20: counter += 1231; break;
        }
    }
    return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}

long long testIf()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = Rand() % 20 + 1;
        if (c == 1) { counter += 20; }
        else if (c == 2) { counter += 33; }
        else if (c == 3) { counter += 62; }
        else if (c == 4) { counter += 15; }
        else if (c == 5) { counter += 416; }
        else if (c == 6) { counter += 3545; }
        else if (c == 7) { counter += 23; }
        else if (c == 8) { counter += 81; }
        else if (c == 9) { counter += 256; }
        else if (c == 10) { counter += 15865; }
        else if (c == 11) { counter += 3234; }
        else if (c == 12) { counter += 22345; }
        else if (c == 13) { counter += 1242; }
        else if (c == 14) { counter += 12341; }
        else if (c == 15) { counter += 41; }
        else if (c == 16) { counter += 34321; }
        else if (c == 17) { counter += 232; }
        else if (c == 18) { counter += 144231; }
        else if (c == 19) { counter += 32; }
        else if (c == 20) { counter += 1231; }
    }
    return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}

int main()
{
    srand(time(NULL));
    printf("Starting...\n");
    printf("Switch statement: %lld ms\n", testSwitch()); fflush(stdout);
    printf("counter: %d\n", counter);
    counter = 0;
    srand(time(NULL));
    printf("If     statement: %lld ms\n", testIf()); fflush(stdout);
    printf("counter: %d\n", counter);
} 

スイッチ:3740
if:3980

(複数回の試行で同様の結果)

また、ケース/ ifの数を5に減らしましたが、スイッチ機能はまだ勝ちました。

13
BobTurbo

MSVCなどの優れた最適化コンパイラは、以下を生成できます。

  1. ケースがニースの長い範囲に配置されている場合は、単純なジャンプテーブル
  2. 多数のギャップがある場合のスパース(2レベル)ジャンプテーブル
  3. ケースの数が少ないか、値が近くにない場合の一連のif
  4. ケースが狭い間隔の複数のグループを表す場合、上記の組み合わせ。

要するに、スイッチが一連のifよりも遅いように見える場合、コンパイラはそれを単に1に変換するだけです。そして、各ケースの単なる比較のシーケンスではなく、バイナリ検索ツリーになる可能性があります。例については here を参照してください。

7
Igor Skochinsky

2)に答えて、一般的なコメントをします。 2)いいえ、投稿したアセンブリコードにジャンプテーブルはありません。ジャンプテーブルは、ジャンプ先のテーブルと、テーブルからインデックス付きの場所に直接ジャンプするための1つまたは2つの命令です。切り替え先が多数ある場合は、ジャンプテーブルの方が理にかなっています。たぶん、オプティマイザーは、宛先の数が何らかのしきい値を超えない限り、else if if logicの方が高速であるということを知っています。 4ではなく20の可能性を使用して例をもう一度試してください。

5
Bill Forster

興味をそそられたので、switchステートメントをより速く実行するためにあなたの例について何を変更できるかを見てみました。

40個のifステートメントを取得し、0個のケースを追加すると、ifブロックの実行は同等のswitchステートメントよりも遅くなります。ここに結果があります: https://www.ideone.com/KZeCz

0のケースを削除する効果は、ここで見ることができます: https://www.ideone.com/LFnrX

4
Ryan Gross

古い(現在は見つけにくい)bench ++ベンチマークの結果の一部を次に示します。

Test Name:   F000003                         Class Name:  Style
CPU Time:       0.781  nanoseconds           plus or minus     0.0715
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 2-way if/else if statement
 compare this test with F000004

Test Name:   F000004                         Class Name:  Style
CPU Time:        1.53  nanoseconds           plus or minus     0.0767
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 2-way switch statement
 compare this test with F000003

Test Name:   F000005                         Class Name:  Style
CPU Time:        7.70  nanoseconds           plus or minus      0.385
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way if/else if statement
 compare this test with F000006

Test Name:   F000006                         Class Name:  Style
CPU Time:        2.00  nanoseconds           plus or minus     0.0999
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way switch statement
 compare this test with F000005

Test Name:   F000007                         Class Name:  Style
CPU Time:        3.41  nanoseconds           plus or minus      0.171
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way sparse switch statement
 compare this test with F000005 and F000006

これからわか​​ることは、(このコンパイラでは、このコンパイラで-VC++ 9.0 x64)、各ifテストには約0.7ナノ秒かかります。テストの数が増えると、時間はほぼ完全に線形になります。

Switchステートメントを使用すると、値が密である限り、2ウェイテストと10ウェイテストの速度にalmost違いはありません。スパース値を使用した10ウェイテストは、デンス値を使用した10ウェイテストの約1.6倍の時間がかかりますが、スパース値を使用しても、10ウェイif/else ifの2倍の速度よりも優れています。

結論:4方向のテストのみを使用しても、switchif/elseのパフォーマンスについては実際には表示されませんmuch。このコードの数値を見ると、4ウェイテストの場合、2つがpretty同様の結果(ifに対して〜2.8ナノ秒)を生成することが予想されるという事実を簡単に補間できます/ elseswitchの場合は〜2.0)。

3
Jerry Coffin

スイッチがジャンプテーブルにコンパイルされていない場合、スイッチよりも効率的にifを書くことができることに注意してください...

(1)すべてのNについてテストする最悪のケースではなく、ケースに順序がある場合、ifまたはifをテストして上半分または下半分にテストし、その半分ごとにバイナリ検索スタイルで...最悪の場合は、N(2)特定のケース/グループが他のケースよりもはるかに頻繁に発生する場合、それらのケースを最初に分離するようにifを設計すると、平均時間を短縮できます。

2
Brian Kennedy

ただし、なぜ速いのか、遅いのかはわかりません。

実際に説明するのはそれほど難しくありません...誤って予測されたブランチは、正しく予測されたブランチよりも数十倍から数百倍高いことを覚えているなら。

% 20バージョンでは、最初のcase/ifが常にヒットします。最近のCPUは、通常どの分岐が使用され、どの分岐が使用されないかを「学習」するため、ループのほぼすべての反復でこの分岐がどのように動作するかを簡単に予測できます。 「if」バージョンが飛ぶ理由を説明しています。最初のテストを過ぎて何も実行する必要はなく、ほとんどの反復でそのテストの結果を(正しく)予測します。明らかに、「スイッチ」の実装はわずかに異なります。おそらくジャンプテーブルでさえ、計算されたブランチのおかげで遅くなる可能性があります。

% 21バージョンでは、ブランチは本質的にランダムです。したがって、それらの多くがすべての反復を実行するだけでなく、CPUはどちらに進むかを推測できません。これは、ジャンプテーブル(または他の「スイッチ」最適化)が役立つ可能性が高い場合です。

最新のコンパイラとCPUでコードがどのように実行されるかを予測することは非常に難しく、世代を重ねるごとに難しくなります。最善のアドバイスは、「試行錯誤することなく、常にプロファイルを作成する」ことです。そのアドバイスはより良くなり、それを無視することができる人々の集合は毎年小さくなります。

これらすべては、上記の私の説明はほとんど推測であると言うことです。 :-)

2
Nemo

これらは、もし他のジャンプなら、他のジャンプなら...ジャンプテーブルはアドレスのテーブルを持っているか、ハッシュまたはそのようなものを使用しています。

速くても遅くても主観的です。たとえば、ケース1を最初ではなく最後にすることができます。テストプログラムまたは実際のプログラムでほとんどの場合、この実装ではコードが遅くなります。そのため、実装に応じてケースリストを再配置するだけで大​​きな違いが得られます。

1〜4ではなく0〜3のケースを使用した場合、コンパイラはジャンプテーブルを使用した可能性がありますが、コンパイラはとにかく+1を削除する必要があります。おそらくそれは少数のアイテムだったのでしょう。たとえば、0〜15または0〜31にした場合、テーブルで実装したり、他のショートカットを使用した可能性があります。コンパイラは、ソースコードの機能を満たす限り、実装方法を自由に選択できます。そして、これはコンパイラの違い、バージョンの違い、最適化の違いになります。ジャンプテーブルが必要な場合は、ジャンプテーブルを作成します。if-then-elseツリーが必要な場合は、if-then-elseツリーを作成します。コンパイラに決定させる場合は、switch/caseステートメントを使用します。

2
old_timer

無し。アセンブラにアクセスして実際のパフォーマンスを測定するほとんどの特定のケースでは、質問は間違っています。与えられた例では、あなたの思考は決定的に短すぎます

counter += (4 - counter % 4);

私はあなたが使用すべき正しい増分式であるように見えます。

1
Jens Gustedt