条件运算符是否缓慢?

Pet*_*old 25 c# performance if-statement conditional-operator micro-optimization

我正在查看一些带有巨大的switch语句和每个case的if-else语句的代码,并立即感受到优化的冲动.作为一个优秀的开发人员,我总是应该着手获得一些硬性时序事实,并从三个变体开始:

  1. 原始代码如下所示:

    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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 第二个变体转换为使用条件运算符:

    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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 使用预先填充了键/字符对的字典进行扭曲:

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

注意:两个switch语句具有完全相同的情况,字典具有相同数量的字符.

我期待1)和2)在性能上有些相似,3)会稍慢.

对于每次运行两次10.000.000迭代进行预热然后定时的方法,令我惊讶的是我得到以下结果:

  1. 每次通话0.0000166毫秒
  2. 每次通话0.0000779毫秒
  3. 每次通话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 Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

更新2:进一步挖掘,我已经查看了为1)和2)生成的IL,并发现主开关结构与我预期的相同,但是案例体略有不同.这是我正在看的IL:

1)如果/ 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 
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

一些观察:

  • 条件运算符在shift等于true时分支,而if/else分支shift则为false.
  • 虽然1)实际上编译的指令多于2),但是当shift为真或假时执行的指令数对于两者是相等的.
  • 1)的指令排序使得始终只占用一个堆栈槽,而2)总是加载两个.

这些观察中的任何一个意味着条件运算符的执行速度会变慢吗?是否有其他副作用发挥作用?

Zyp*_*rax 12

很奇怪,也许.NET优化会在你的情况下适得其反:

作者反汇编了三个版本的三元表达式,发现它们与if语句完全相同,只有一个小的区别.三元语句有时会生成代码来测试您期望的相反条件,因为它测试子表达式是否为false而不是测试是否为真.这重新排序了一些指令,偶尔可以提高性能.

http://dotnetperls.com/ternary

您想要考虑枚举值上的ToString(对于非特殊情况):

string keyValue = inKey.ToString();
return shift ? keyValue : keyValue.ToLower();
Run Code Online (Sandbox Code Playgroud)

编辑:
我已经将if-else方法与三元运算符进行了比较,并且在1000000个周期内,三元运算符始终至少与if-else方法一样快(有时快几毫秒,支持上面的文本).我认为你在测量花费的时间上犯了一些错误.

  • 我做了一些测试,当你投入.ToString(),性能坦克.字符串操作是一些最慢的,并且在原始示例中手动测试显式值和转换,而更详细,明显更快. (2认同)

jri*_*sta 11

我很想知道您是否使用Debug或Release版本进行测试.如果它是调试版本,那么由于编译器在使用发布模式时添加的低级优化的LACK(或者手动禁用调试模式并启用编译器优化),差异很可能是差异.

但是,我希望通过优化,三元运算符的速度与if/else语句的速度相同或稍快,而字典查找速度最慢.以下是我的结果,1000万个热身迭代,然后是1000万个定时,每个:

调试模式

   If/Else: 00:00:00.7211259
   Ternary: 00:00:00.7923924
Dictionary: 00:00:02.3319567
Run Code Online (Sandbox Code Playgroud)

发布模式

   If/Else: 00:00:00.5217478
   Ternary: 00:00:00.5050474
Dictionary: 00:00:02.7389423
Run Code Online (Sandbox Code Playgroud)

我认为这里有趣的是,在启用优化之前,三元计算比if/else慢,而之后,它更快.

编辑:

经过一些测试,在实际意义上,if/else和ternary之间几乎没有区别.虽然三元代码导致较小的IL,但它们的表现几乎相同.在具有释放模式二进制的十二种不同测试中,if/else和三元结果要么相同,要么在10,000,000次迭代中关闭几分之一毫秒.有时候if/else会稍快一点,有时甚至是三元组,但实际上,它们表现相同.

另一方面,词典的表现要差得多.当谈到这些优化时,如果代码已经存在,我不会浪费时间在if/else和ternary之间进行选择.但是,如果你现在有一个字典实现,我肯定会重构它以使用更有效的方法,并将你的性能提高大约400%(对于给定的函数,无论如何.)