本文介绍了条件运算符很慢吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在查看一些带有大量 switch 语句和每个 case 的 if-else 语句的代码,并立即感受到了优化的冲动.作为一名优秀的开发人员,我总是应该做的,我开始着手获取一些难以把握的时间事实,并从三个变体开始:

I was looking at some code with a huge switch statement and an if-else statement on each case and instantly felt the urge to optimize. As a good developer always should do I set out to get some hard timing facts and started with three variants:

  1. 原来的代码是这样的:

  1. The original code looks like this:

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;
}

  • 转换为使用条件运算符的第二个变体:

  • The second variant converted to use the conditional operator:

    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;
    }
    

  • 使用预先填充有键/字符对的字典的一种扭曲:

  • A twist using a dictionary pre-filled with key/character pairs:

    public static bool DictionaryLookup(Key inKey, out char key, bool shift)
    {
        key = '';
        if (shift)
            return _upperKeys.TryGetValue(inKey, out key);
        else
            return _lowerKeys.TryGetValue(inKey, out key);
    }
    

  • 注意:两个switch语句的大小写完全相同,字典的字符数相同.

    Note: the two switch statements have the exact same cases and the dictionaries have an equal amount of characters.

    我原以为 1) 和 2) 在性能上有些相似,而 3) 会稍微慢一些.

    I was expecting that 1) and 2) was somewhat similar in performance and that 3) would be slightly slower.

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

    For each method running two times 10.000.000 iterations for warm-up and then timed, to my amazement I get the following results:

    1. 每次调用 0.0000166 毫秒
    2. 每次调用 0.0000779 毫秒
    3. 每次调用 0.0000413 毫秒

    这怎么可能?条件运算符比 if-else 语句慢四倍,​​比字典查找慢近两倍.我在这里遗漏了一些重要的东西,还是条件运算符本身很慢?

    How can this be? The conditional operator is four times slower than if-else statements and almost two times slower than dictionary look-ups. Am I missing something essential here or is the conditional operator inherently slow?

    更新 1: 关于我的测试工具的几句话.我在 Visual Studio 2010 中的 Release 编译的 .Net 3.5 项目下为上述每个变体运行以下(伪)代码.代码优化已打开,DEBUG/TRACE 常量已关闭.在进行计时运行之前,我会运行一次测量中的方法进行热身.run 方法执行了大量迭代的方法,将 shift 设置为 true 和 false,并使用一组选择输入键:

    Update 1: A few words about my test harness. I run the following (pseudo)code for each of the above variants under a Release compiled .Net 3.5 project in Visual Studio 2010. Code optimization is turned on and DEBUG/TRACE constants are turned off. I run the method under measurement once for warm-up before doing a timed run. The run method executed the method for a large number of iterations, with shift set to both true and false and with a select set of input keys:

    Run(method);
    var stopwatch = Stopwatch.StartNew();
    Run(method);
    stopwatch.Stop();
    var measure = stopwatch.ElapsedMilliseconds / iterations;
    

    Run 方法如下所示:

    The Run method looks like this:

    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:

    Update 2: Digging further, I have looked at the IL generated for 1) and 2) and find that the main switch structures are identical as I would expect, yet the case bodies have slight differences. Here is the IL I'm looking at:

    1) If/else 语句:

    1) If/else statement:

    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) 总是加载两个.
    • The conditional operator branches when shift equals true while if/else branches when shift is false.
    • While 1) actually compiles to a few more instructions than 2), the number of instructions executed when shift is either true or false, are equal for the two.
    • The instruction ordering for 1) is such that only one stack slot is occupied at all times, while 2) always loads two.

    这些观察是否意味着条件运算符的执行速度会变慢?还有其他副作用吗?

    Do any of these observations imply that the conditional operator will perform slower? Is there other side-effects that come into play?

    推荐答案

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

    Very odd, perhaps .NET optimization is backfireing in your case:

    作者拆解了几个三元表达式的版本和发现它们与if 语句,有一个小不同之处.三元语句有时会生成测试相反的条件,你会期望,因为在它测试子表达式为假而不是测试是否属实.这重新排序一些说明,可以偶尔提高性能.

    http://dotnetperls.com/ternary

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

    You want might consider the ToString on the enum value (for the non-special cases):

    string keyValue = inKey.ToString();
    return shift ? keyValue : keyValue.ToLower();
    


    我已经将 if-else 方法与三元运算符进行了比较,并且在 1000000 次循环中,三元运算符始终至少与 if-else 方法一样快(有时快几毫秒,支持上述文本).我认为您在计算所花费的时间时犯了某种错误.


    I've compared the if-else method with the ternary operator and with 1000000 cycles the ternary operator is always at least as fast as the if-else method (sometimes a few millisec faster, which supports the text above). I think that you've made somekind of error in measuring the time it took.

    这篇关于条件运算符很慢吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

    08-19 15:19
    查看更多