关于C#:”switch”比”if”快吗?

Is 'switch' faster than 'if'?

一个switch语句实际上比一个if语句快吗?

我在VisualStudio 2010的X64 C++编译器上运行了代码,其中EDCOX1引用2标记:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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...
"
);
    printf("Switch statement: %u ms
"
, testSwitch());
    printf("If     statement: %u ms
"
, testIf());
}

得到这些结果:

Switch statement: 5261 ms
If statement: 5196 ms

据我所知,switch语句显然使用跳转表来优化分支。

问题:

  • X86或X64中的基本跳转表是什么样子的?

  • 此代码是否使用跳转表?

  • 为什么这个例子中没有性能差异?在任何情况下,是否存在显著的性能差异?

  • 代码的分解:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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

    更新:

    有趣的结果。但不知道为什么一个人更快,一个人更慢。


    编译器可以在开关上进行几种优化。不过,我不认为经常提到的"跳转表"是非常有用的,因为它只在输入可以以某种方式绑定时工作。

    "跳转表"的C伪代码是这样的——注意,实际上编译器需要在表周围插入某种形式的if测试,以确保输入在表中有效。还要注意,它只在输入是连续数字的情况下工作。

    如果开关中的分支数非常大,编译器可以对开关的值进行二进制搜索之类的操作,这(在我看来)将是一种更有用的优化,因为它在某些情况下会显著提高性能,与开关一样通用,并且不会导致生成的代码更大。但要看到这一点,您的测试代码需要更多的分支来查看任何差异。

    要回答您的特定问题:

  • Clang生成了一个如下所示的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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
  • 我可以说它没有使用跳转表——4个比较指令清晰可见:

    1
    2
    3
    4
    5
    6
    7
    8
    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)

    基于跳转表的解决方案根本不使用比较。

  • 或者没有足够的分支来导致编译器生成跳转表,或者编译器根本没有生成它们。我不确定是哪一个。
  • 编辑2014:熟悉LLVM优化器的人在其他地方进行了一些讨论,认为跳转表优化在许多情况下都很重要;例如,在所述枚举中存在多个值的枚举和许多针对值的枚举的情况下。也就是说,我支持我在2011年所说的话——我经常看到人们在想,"如果我把它变成一个开关,不管我有多少个案例,它都将是同一时间"——这完全是错误的。即使使用跳转表,您也会得到间接跳转成本,并为每种情况支付表中的条目;在现代硬件上,内存带宽是一个很大的问题。

    为可读性编写代码。任何值得一试的编译器都将看到if/else if梯形图,并将其转换为等效的开关,反之亦然(如果这样做更快)。


    关于你的问题:

    1.在x86或x64中,基本跳转表是什么样子的?

    跳转表是存储指向类似数组结构中标签的指针的内存地址。下面的示例将帮助您了解跳转表的外观

    1
    2
    3
    4
    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检查序列,那么在这种情况下,使用跳转表可以降低性能命中,但这会带来内存成本。

    座右铭:编译器足够聪明处理这种情况:)


    编译器可以自由地将switch语句编译为等同于if语句的代码,或者创建跳转表。根据编译器选项中指定的内容,它可能会根据执行速度最快的代码或生成最小的代码来选择一个或另一个,所以最坏的情况是,它的速度与if语句的速度相同

    我相信编译器会做最好的选择,并专注于什么使代码最可读。

    如果事例数变得非常大,跳转表将比一系列if快得多。但是,如果值之间的步骤非常大,则跳转表可能会变大,编译器可能会选择不生成跳转表。


    您如何知道您的计算机在切换测试循环期间没有执行与测试无关的任务,在if测试循环期间执行的任务更少?您的测试结果不显示以下内容:

  • 差别很小
  • 只有一个结果,而不是一系列结果
  • 案件太少了
  • 我的结果是:

    我补充说:

    1
    2
    printf("counter: %u
    "
    , counter);

    最后,这样它就不会优化循环,因为在您的示例中从未使用过计数器,所以编译器为什么要执行循环?立即,即使有了这样一个微观基准,这种转变也总是会取得胜利。

    代码的另一个问题是:

    1
    switch (counter % 4 + 1)

    在你的开关回路中,与

    1
    const size_t c = counter % 4 + 1;

    在你的if循环中。如果你能解决这个问题,那就大不一样了。我相信,将语句放入switch语句会激发编译器将值直接发送到CPU寄存器中,而不是先将其放入堆栈中。因此,这有利于switch语句,而不是平衡测试。

    哦,我认为你也应该在测试之间重置计数器。实际上,您可能应该使用某种随机数,而不是+1、+2、+3等,因为它可能会优化其中的一些内容。例如,随机数是指基于当前时间的数字。否则,编译器可以将这两个函数都转换成一个长的数学运算,甚至不需要任何循环。

    我对Ryan的代码进行了足够的修改,以确保编译器无法在代码运行之前解决问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    #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...
    "
    );
        printf("Switch statement: %lld ms
    "
    , testSwitch()); fflush(stdout);
        printf("counter: %d
    "
    , counter);
        counter = 0;
        srand(time(NULL));
        printf("If     statement: %lld ms
    "
    , testIf()); fflush(stdout);
        printf("counter: %d
    "
    , counter);
    }

    开关:3740如果:3980

    (多次尝试的结果相似)

    我还将cases/ifs的数量减少到了5个,切换功能仍然获胜。


    一个好的优化编译器(如msvc)可以生成:

  • 一个简单的跳台,如果箱子排列在一个很长的范围内
  • 一个稀疏(两级)跳转表,如果有许多间隙
  • 如果事例数很小或值很小,则为一系列国际单项体育联合会不紧密地联系在一起
  • 如果案例代表多组间距很近的范围。
  • 简而言之,如果开关看起来比一系列IFS慢,编译器可能只是将其转换为一个。它可能不仅仅是每种情况的一个比较序列,而是一个二进制搜索树。请参阅此处以获取示例。


    我会回答2)并做一些一般性的评论。2)不,您发布的程序集代码中没有跳转表。跳转表是一个包含跳转目的地的表,以及一个或两个直接从表跳转到索引位置的指令。当有许多可能的交换目的地时,跳转表会更有意义。也许乐观者知道简单的if-else逻辑更快,除非目的地的数量大于某个阈值。用"说出20个可能性"而不是"4"来再次尝试您的示例。


    我很感兴趣,并查看了可以对您的示例进行哪些更改,以使它更快地运行switch语句。

    如果您得到40个if语句,并添加一个0大小写,那么if块的运行速度将比等效的switch语句慢。我这里有结果:https://www.ideone.com/kzecz。

    删除0案例的效果可以在这里看到:https://www.ideone.com/lfnrx。


    下面是旧的(现在很难找到)bench++基准测试的一些结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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.0x64),每个if测试大约需要0.7纳秒。随着测试次数的增加,时间刻度几乎成线性。

    使用switch语句,只要值密集,2路测试和10路测试的速度几乎没有差别。稀疏值10路测试的时间约为密集值10路测试的1.6倍,但即使是稀疏值,仍然比10路if/else if的速度快两倍。

    底线:仅使用4路测试并不能真正显示出switchifelse的性能。如果你看一下这段代码中的数字,很容易得出这样一个事实:对于4路测试,我们希望这两个测试产生非常相似的结果(对于ifelse/2.8纳秒,对于switch/2.0纳秒)。


    请注意,当一个开关没有编译成跳转表时,您可以经常写if比开关更有效…

    (1)如果案例有一个顺序,而不是所有n的最坏案例测试,那么你可以编写if's来测试if在上半部分或下半部分,然后在每半部分中,使用二进制搜索样式…导致最坏的情况是logn而不是n

    (2)如果某些案例/群体比其他案例频繁得多,那么设计您的国际单项体育联合会,首先隔离这些案例,可以加快平均时间通过


    Not sure why one is faster and one is slower, though.

    这其实不难解释…如果你记得预测失误的分支比正确预测的分支贵几十到几百倍。

    % 20版本中,第一个case/if总是命中的。现代CPU"学习"通常采用哪些分支,而不是哪些分支,因此它们可以很容易地预测该分支在几乎每次循环迭代中的行为。这就解释了为什么"if"版本会运行;它从不需要执行第一个测试之后的任何操作,并且它(正确地)预测了该测试在大多数迭代中的结果。显然,"switch"的实现方式略有不同——甚至可能是一个跳转表,由于计算出的分支,它可能会很慢。

    % 21版本中,分支基本上是随机的。因此,它们中的许多不仅执行每次迭代,CPU也无法猜测它们将朝哪个方向发展。在这种情况下,跳转表(或其他"切换"优化)可能会有所帮助。

    很难预测一段代码如何使用现代编译器和CPU来执行,每一代代码都会变得更加困难。最好的建议是"不要费心去尝试;总是做个侧面的人"。这种建议会越来越好,而那些能忽视它的人每年都会越来越少。

    所有这些都是说,我上面的解释很大程度上是一种猜测。-)


    不,这些是if-then-jump-else-if-then-jump-else…一个跳转表将有一个地址表,或者使用哈希或类似的东西。

    快或慢是主观的。例如,您可以让case 1是最后一件事,而不是第一件事,如果您的测试程序或现实世界程序使用case 1,那么大多数情况下,代码在这个实现中会变慢。因此,只需重新安排案例列表,根据实现情况,就可以产生很大的不同。

    如果您使用的是0-3而不是1-4,那么编译器可能使用了一个跳转表,编译器应该已经计算出删除+1了。可能是因为物品数量太少。例如,如果您将其设置为0-15或0-31,它可能使用了一个表来实现它,或者使用了其他一些快捷方式。只要编译器满足源代码的功能,它就可以自由选择如何实现这些东西。这就涉及到编译器的差异、版本的差异和优化的差异。如果你想要一个跳转表,做一个跳转表,如果你想要一个if-then-else树,做一个if-then-else树。如果要让编译器决定,请使用switch/case语句。


    一个也没有。在大多数特定的情况下,当您进入汇编程序并进行实际的性能度量时,您的问题仅仅是一个错误的问题。对于给定的例子来说,你的想法显然太短了,因为

    1
    counter += (4 - counter % 4);

    在我看来,这是您应该使用的正确增量表达式。