关于C#:if与switch的速度比较

If vs. Switch Speed

由于编译器优化,switch语句通常比等效if-else if语句(如本文中所述)更快。

这种优化实际上是如何工作的?有人能解释清楚吗?


编译器可以在适当的地方构建跳转表。例如,当您使用反射镜查看生成的代码时,您将看到对于字符串上的巨大开关,编译器将实际生成使用哈希表来调度这些代码的代码。哈希表使用字符串作为键,并将case代码的委托作为值。

这比许多链接的if测试具有更好的渐进运行时,实际上甚至对于相对较少的字符串也更快。


康拉德是对的。在打开整数的连续范围的情况下(例如,在这里您有0,1,2..案例n),编译器可以做得更好,因为它甚至不需要构建哈希表;它只存储一个函数指针数组,因此可以在恒定时间内加载其跳转目标。


这是一个轻微的简化,因为通常任何现代编译器都会遇到一个if..else if ..序列,这个序列可以被一个人转换成switch语句,编译器也会这样做。但是为了增加额外的乐趣,编译器不受语法限制,因此可以在内部生成类似于switch的语句,这些语句混合了范围、单个目标等,并且它们可以(并且可以)为switch和if..else语句都这样做。

总之,康拉德答案的一个扩展是编译器可以生成一个跳转表,但这不一定保证(也不可取)。由于各种原因,跳转表对现代处理器上的分支预测器做了坏事,而表本身也做了坏事来缓存行为,例如。

1
switch(a) { case 0: ...; break; case 1: ...; break; }

如果编译器真的为此生成了一个跳转表,那么由于跳转表破坏了分支预测,替代的if..else if..样式的代码可能会慢一些。


正如康拉德所说,编译器可以构建一个跳转表。

在C++中,它之所以可以是因为交换机的限制。

  • 比较项必须转换为int。
  • case labl必须是常量。

switch/case语句通常速度更快,深度为1级,但当您开始进入2个或更多级别时,switch/case语句开始占用的时间是嵌套if/else语句的2-3倍。

本文进行了一些速度比较,突出了嵌套此类语句时的速度差异。

例如,根据他们的测试,示例代码如下:

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
if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

在运行等效switch/case语句的一半时间内完成:

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
switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

是的,这是一个基本的例子,但它说明了这一点。

因此,结论可能是对只有一个层次深度的简单类型使用switch/case,但对于更复杂的比较和多个嵌套层次,使用经典的if/else构造?


不匹配的统计数据可能不好。

如果您实际下载了源代码,那么在if和switch的情况下,no-match值都是21。编译器应该能够进行抽象,知道应该始终运行哪个语句,并且CPU应该能够正确地进行分支预测。

更有趣的是,在我看来,并非每一个案例都会破裂,但这可能不是实验的范围。


if-over-case的唯一优点是当第一个case的出现频率显著增加时。

不确定阈值的确切位置,但是我使用case语法,除非第一个"几乎总是"通过第一个测试。