不要将 C# switch 语句与 CIL switch 指令混淆,这一点很重要。
CIL 开关是一个跳转表,它需要一个指向一组跳转地址的索引。
这仅在 C# 开关的 case 相邻时才有用:
case 3: blah; break;
case 4: blah; break;
case 5: blah; break;
但如果他们不是:
case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;
(您需要一个大小约为 3000 个条目的表,仅使用 3 个插槽)
对于不相邻的表达式,编译器可能会开始执行线性 if-else-if-else 检查。
对于较大的非相邻表达式集,编译器可能会从二叉树搜索开始,最后是 if-else-if-else 最后几项。
对于包含相邻项簇的表达式集,编译器可以进行二叉树搜索,最后进行 CIL 切换。
这充满了“可能”和“可能”,并且取决于编译器(可能与 Mono 或 Rotor 不同)。
我使用相邻案例在我的机器上复制了您的结果:
执行 10 路开关的总时间,10000 次迭代(毫秒):25.1383
每 10 路开关的近似时间(毫秒):0.00251383
执行 50 路切换的总时间,10000 次迭代(毫秒):26.593
每 50 路切换的近似时间(毫秒):0.0026593
执行 5000 路切换的总时间,10000 次迭代(毫秒):23.7094
每个 5000 路切换的近似时间(毫秒):0.00237094
执行 50000 路切换的总时间,10000 次迭代(毫秒):20.0933
每个 50000 路切换的近似时间(毫秒):0.00200933
然后我还使用了不相邻的 case 表达式:
执行 10 路开关的总时间,10000 次迭代(毫秒):19.6189
每 10 路开关的近似时间(毫秒):0.00196189
执行 500 路切换的总时间,10000 次迭代(毫秒):19.1664
每个 500 路切换的近似时间(毫秒):0.00191664
执行 5000 路切换的总时间,10000 次迭代(毫秒):19.5871
每个 5000 路切换的近似时间(毫秒):0.00195871
不相邻的 50,000 个 case switch 语句将无法编译。
“表达式太长或太复杂,无法在 'ConsoleApplication1.Program.Main(string[])' 附近编译
这里有趣的是,二叉树搜索似乎比 CIL 切换指令快一点(可能不是统计上的)。
Brian,您使用了“常数”这个词,从计算复杂性理论的角度来看,它具有非常明确的含义。虽然简单的相邻整数示例可能会产生被视为 O(1)(常数)的 CIL,但稀疏示例为 O(log n)(对数),聚类示例介于两者之间,小示例为 O(n)(线性)。
这甚至不能解决 String 的情况,在这种情况下Generic.Dictionary<string,int32>
可能会创建一个静态对象,并且在首次使用时会遭受一定的开销。这里的性能将取决于Generic.Dictionary
.
如果您检查C# 语言规范(而不是 CIL 规范),您会发现“15.7.2 switch 语句”没有提及“恒定时间”,或者底层实现甚至使用 CIL switch 指令(要非常小心地假设这样的事情)。
归根结底,在现代系统上针对整数表达式的 C# 切换是亚微秒级的操作,通常不值得担心。
当然,这些时间将取决于机器和条件。我不会关注这些时序测试,我们正在谈论的微秒持续时间与正在运行的任何“真实”代码相比相形见绌(并且您必须包含一些“真实代码”,否则编译器会将分支优化掉),或者系统抖动。我的答案基于使用IL DASM检查 C# 编译器创建的 CIL。当然,这不是最终的,因为 CPU 运行的实际指令是由 JIT 创建的。
我检查了在我的 x86 机器上实际执行的最终 CPU 指令,并且可以确认一个简单的相邻设置开关执行以下操作:
jmp ds:300025F0[eax*4]
二叉树搜索充满了:
cmp ebx, 79Eh
jg 3000352B
cmp ebx, 654h
jg 300032BB
…
cmp ebx, 0F82h
jz 30005EEE