【问题标题】:Are .Net switch statements hashed or indexed?.Net switch 语句是散列还是索引?
【发布时间】:2011-03-22 22:28:45
【问题描述】:

.Net 4(或任何先前版本)是否对基于字符串的较长 switch 语句执行任何类型的优化?

我正在解决一个潜在的性能瓶颈,因为一些长的 switch 语句在案例中寻找匹配的字符串,我一直假设这些是在线性时间(或接近线性,即不使用索引来快速搜索)找到匹配的字符串)。但这似乎是.Net可以优化的一个明显领域,所以我想我会检查是否是这种情况。

这是我最近的一个衍生问题:indexed switch statement, or equivalent? .net, C#

【问题讨论】:

  • 你看到我的回答了吗?我特别提到,如果 case 语句的数量达到某个阈值,则字符串上的 switch 语句将优化为 Dictionary 查找。
  • 布赖恩,刚刚看到你帖子的那一部分。你能指点我这方面的文件吗?我正在寻找相互矛盾的答案。感谢您的帮助。
  • 您将很难找到有关该主题的官方文档(除了偶尔发布的博客文章之外)。原因是因为这是一个实现细节。

标签: c# .net switch-statement


【解决方案1】:

编译以下代码。

public static int Main(string[] args)
{
    switch (args[0])
    {
        case "x": return 1;
        case "y": return 2;
        case "z": return 3;
    }
    return 0;
}

现在使用 ReflectorILDASM 检查 C# 编译器生成的 IL。继续添加case语句,反编译观察结果。

  • 如果 case 语句的数量很少,则编译器会发出顺序相等比较。
  • 如果 case 语句的数量很大,则编译器会发出 Dictionary 查找。

我使用的是 C# 3.0 编译器,我观察到策略在 7 个 case 语句处发生了变化。我怀疑你会看到与 C# 4.0 和其他类似的东西。

更新:

我应该指出,您将在 IL 输出中看到对 Dictionary.Add 的调用,它正在构建字典以供以后使用。不要被愚弄以为每次都会发生这种情况。编译器实际上正在生成一个单独的静态类并对其进行内联静态初始化。请特别注意 L_0026 处的说明。如果该类已经初始化,那么分支将跳过 Add 调用。

L_0021: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> <PrivateImplementationDetails>{816396DD-F271-4C12-83D0-CC9C9CD67AD6}::$$method0x6000001-1
L_0026: brtrue.s L_0089
L_0028: ldc.i4.7 
L_0029: newobj instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::.ctor(int32)
L_002e: dup 
L_002f: ldstr "x"
L_0034: ldc.i4.0 
L_0035: call instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
L_003a: dup 
L_003b: ldstr "y"
L_0040: ldc.i4.1 
L_0041: call instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
L_0046: dup 
L_0047: ldstr "z"
L_004c: ldc.i4.2 
L_004d: call instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)

另外,请注意字典实际上包含从原始字符串到整数的映射。这个整数用于在 IL 中制定一个单独的开关。

L_0089: volatile. 
L_008b: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> <PrivateImplementationDetails>{816396DD-F271-4C12-83D0-CC9C9CD67AD6}::$$method0x6000001-1
L_0090: ldloc.2 
L_0091: ldloca.s CS$0$0002
L_0093: call instance bool [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::TryGetValue(!0, !1&)
L_0098: brfalse.s L_00da
L_009a: ldloc.3 
L_009b: switch (L_00be, L_00c2, L_00c6, L_00ca, L_00ce, L_00d2, L_00d6)
L_00bc: br.s L_00da
L_00be: ldc.i4.1 
L_00bf: stloc.1 
L_00c0: br.s L_00de
L_00c2: ldc.i4.2 
L_00c3: stloc.1 
L_00c4: br.s L_00de
L_00c6: ldc.i4.3 

更新 2:

对于它的价值,VB.NET 似乎没有对其Select 构造进行同样的优化。

【讨论】:

  • 我记得读过一篇关于这种优化的旧文章 (.Net 1)。它还与使用 IsInterned 语句和使用引用比较有关。但我似乎找不到这篇文章。
  • 仅供参考 CSC 4.0 似乎也在 7 个案例中切换(尽管显然不应该依赖这个!)
  • +1。关于更新 2:C#case 语句必须是常量表达式,因此 C# 编译器可以使用这种缓存的 Dictionary 方法。 VB.Net Case 语句可以是表达式,因此 VB 编译器不能在一般情况下使用该策略。 (@Brian 是我对您关于此主题的其他答案的评论的副本!)
  • @MarkJ 但是当所有情况都是常量时它可以!
【解决方案2】:

看起来较新的编译器使用ComputeStringHash(),然后在哈希命中而不是字典构造上进行字符串比较。

// [19 6 - 19 22]
IL_0037: ldarg.0      // args
IL_0038: ldc.i4.0     
IL_0039: ldelem.ref   
IL_003a: stloc.s      V_5

IL_003c: ldloc.s      V_5
IL_003e: call         unsigned int32 '<PrivateImplementationDetails>'::ComputeStringHash(string)
IL_0043: stloc.s      V_6
IL_0045: ldloc.s      V_6
IL_0047: ldc.i4       -502520314 // 0xe20c2606
IL_004c: bgt.un.s     IL_007b
IL_004e: ldloc.s      V_6
IL_0050: ldc.i4       -536075552 // 0xe00c22e0
IL_0055: beq          IL_00f9
IL_005a: br.s         IL_005c
IL_005c: ldloc.s      V_6
IL_005e: ldc.i4       -519297933 // 0xe10c2473
IL_0063: beq          IL_00e9
IL_0068: br.s         IL_006a
IL_006a: ldloc.s      V_6
IL_006c: ldc.i4       -502520314 // 0xe20c2606
IL_0071: beq          IL_0119
IL_0076: br           IL_014c
IL_007b: ldloc.s      V_6
IL_007d: ldc.i4       -468965076 // 0xe40c292c
IL_0082: bgt.un.s     IL_009d
IL_0084: ldloc.s      V_6
IL_0086: ldc.i4       -485742695 // 0xe30c2799
IL_008b: beq.s        IL_0109
IL_008d: br.s         IL_008f
IL_008f: ldloc.s      V_6
IL_0091: ldc.i4       -468965076 // 0xe40c292c
IL_0096: beq.s        IL_00b6
IL_0098: br           IL_014c
IL_009d: ldloc.s      V_6
IL_009f: ldc.i4       -435409838 // 0xe60c2c52
IL_00a4: beq.s        IL_00d9
IL_00a6: br.s         IL_00a8
IL_00a8: ldloc.s      V_6
IL_00aa: ldc.i4       -418632219 // 0xe70c2de5
IL_00af: beq.s        IL_00c9
IL_00b1: br           IL_014c
IL_00b6: ldloc.s      V_5
IL_00b8: ldstr        "a"
IL_00bd: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_00c2: brtrue.s     IL_0129
IL_00c4: br           IL_014c
IL_00c9: ldloc.s      V_5
IL_00cb: ldstr        "b"
IL_00d0: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_00d5: brtrue.s     IL_012e
IL_00d7: br.s         IL_014c
IL_00d9: ldloc.s      V_5
IL_00db: ldstr        "c"
IL_00e0: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_00e5: brtrue.s     IL_0133
IL_00e7: br.s         IL_014c
IL_00e9: ldloc.s      V_5
IL_00eb: ldstr        "d"
IL_00f0: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_00f5: brtrue.s     IL_0138
IL_00f7: br.s         IL_014c
IL_00f9: ldloc.s      V_5
IL_00fb: ldstr        "e"
IL_0100: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_0105: brtrue.s     IL_013d
IL_0107: br.s         IL_014c
IL_0109: ldloc.s      V_5
IL_010b: ldstr        "f"
IL_0110: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_0115: brtrue.s     IL_0142
IL_0117: br.s         IL_014c
IL_0119: ldloc.s      V_5
IL_011b: ldstr        "g"
IL_0120: call         bool [mscorlib]System.String::op_Equality(string, string)
IL_0125: brtrue.s     IL_0147
IL_0127: br.s         IL_014c

// [21 17 - 21 26]
IL_0129: ldc.i4.0     
IL_012a: stloc.s      V_7
IL_012c: br.s         IL_01ac

// [22 17 - 22 26]
IL_012e: ldc.i4.1     
IL_012f: stloc.s      V_7
IL_0131: br.s         IL_01ac

// [23 17 - 23 26]
IL_0133: ldc.i4.2     
IL_0134: stloc.s      V_7
IL_0136: br.s         IL_01ac

// [24 17 - 24 26]
IL_0138: ldc.i4.3     
IL_0139: stloc.s      V_7
IL_013b: br.s         IL_01ac

// [25 17 - 25 26]
IL_013d: ldc.i4.4     
IL_013e: stloc.s      V_7
IL_0140: br.s         IL_01ac

// [26 17 - 26 26]
IL_0142: ldc.i4.5     
IL_0143: stloc.s      V_7
IL_0145: br.s         IL_01ac

// [27 17 - 27 26]
IL_0147: ldc.i4.6     
IL_0148: stloc.s      V_7
IL_014a: br.s         IL_01ac

// [28 16 - 28 26]
IL_014c: ldc.i4.m1    
IL_014d: stloc.s      V_7
IL_014f: br.s         IL_01ac

【讨论】:

  • 来自github.com/dotnet/roslyn:“Roslyn 不使用字典来避免分配和首次执行字符串切换时潜在的巨大损失。Roslyn 使用将字符串映射到哈希码和一个数字开关。从某种意义上说,这是以前使用静态字典的技术的部分内联。”
猜你喜欢
  • 1970-01-01
  • 2011-09-09
  • 2020-11-02
  • 1970-01-01
  • 2011-01-28
  • 1970-01-01
  • 1970-01-01
  • 2011-10-09
  • 1970-01-01
相关资源
最近更新 更多