我正在查看一些代码,其中每个案例都有一个巨大的 switch 语句和一个 if-else 语句,并立即感到优化的冲动。作为一个优秀的开发人员应该做的,我开始着手了解一些时间上的事实,并从三个变体开始:
原始代码如下所示:
public static bool SwitchIfElse(Key inKey, out char key, bool shift) { switch (inKey) { case Key.A: if (shift) { key = 'A'; } else { key = 'a'; } return true; case Key.B: if (shift) { key = 'B'; } else { key = 'b'; } return true; case Key.C: if (shift) { key = 'C'; } else { key = 'c'; } return true; ... case Key.Y: if (shift) { key = 'Y'; } else { key = 'y'; } return true; case Key.Z: if (shift) { key = 'Z'; } else { key = 'z'; } return true; ... //some more cases with special keys... } key = (char)0; return false; }
第二个变体转换为使用条件运算符:
public static bool SwitchConditionalOperator(Key inKey, out char key, bool shift) { switch (inKey) { case Key.A: key = shift ? 'A' : 'a'; return true; case Key.B: key = shift ? 'B' : 'b'; return true; case Key.C: key = shift ? 'C' : 'c'; return true; ... case Key.Y: key = shift ? 'Y' : 'y'; return true; case Key.Z: key = shift ? 'Z' : 'z'; return true; ... //some more cases with special keys... } key = (char)0; return false; }
使用预填充键/字符对的字典的转折:
public static bool DictionaryLookup(Key inKey, out char key, bool shift) { key = '\0'; if (shift) return _upperKeys.TryGetValue(inKey, out key); else return _lowerKeys.TryGetValue(inKey, out key); }
注意:这两个 switch 语句具有完全相同的大小写,并且字典具有相同数量的字符。
我期待 1) 和 2) 在性能上有点相似,而 3) 会稍微慢一些。
对于运行两次 10.000.000 次迭代以进行预热然后计时的每种方法,令我惊讶的是,我得到以下结果:
- 每次调用 0.0000166 毫秒
- 每次调用 0.0000779 毫秒
- 每次调用 0.0000413 毫秒
怎么会这样?条件运算符比 if-else 语句慢四倍,几乎比字典查找慢两倍。我在这里遗漏了一些重要的东西还是条件运算符天生很慢?
更新 1:关于我的测试工具的几句话。我在 Visual Studio 2010 的Release编译 .Net 3.5 项目下为上述每个变体运行以下(伪)代码。代码优化已打开,DEBUG/TRACE 常量已关闭。在进行定时运行之前,我会运行一次被测方法进行热身。run 方法执行该方法进行了大量迭代,shift
设置为 true 和 false,并使用一组选择的输入键:
Run(method);
var stopwatch = Stopwatch.StartNew();
Run(method);
stopwatch.Stop();
var measure = stopwatch.ElapsedMilliseconds / iterations;
Run 方法如下所示:
for (int i = 0; i < iterations / 4; i++)
{
method(Key.Space, key, true);
method(Key.A, key, true);
method(Key.Space, key, false);
method(Key.A, key, false);
}
更新 2:进一步挖掘,我查看了为 1) 和 2) 生成的 IL,发现主开关结构与我预期的相同,但外壳主体略有不同。这是我正在查看的 IL:
1) if/else 语句:
L_0167: ldarg.2
L_0168: brfalse.s L_0170
L_016a: ldarg.1
L_016b: ldc.i4.s 0x42
L_016d: stind.i2
L_016e: br.s L_0174
L_0170: ldarg.1
L_0171: ldc.i4.s 0x62
L_0173: stind.i2
L_0174: ldc.i4.1
L_0175: ret
2)条件运算符:
L_0165: ldarg.1
L_0166: ldarg.2
L_0167: brtrue.s L_016d
L_0169: ldc.i4.s 0x62
L_016b: br.s L_016f
L_016d: ldc.i4.s 0x42
L_016f: stind.i2
L_0170: ldc.i4.1
L_0171: ret
一些观察:
- 条件运算符在
shift
等于 true 时分支,而 if/else 在shift
为 false 时分支。 - 虽然 1) 实际上编译为比 2) 多一些指令,但在
shift
为真或假时执行的指令数对于两者来说是相等的。 - 1) 的指令顺序是始终只占用一个堆栈槽,而 2) 总是加载两个。
这些观察是否暗示条件运算符的执行速度会变慢?还有其他副作用起作用吗?