【问题标题】:Why does the conditional (ternary) operator seem significantly faster?为什么条件(三元)运算符看起来明显更快?
【发布时间】:2012-09-17 15:24:56
【问题描述】:

编辑

如果我正确使用 Stopwatch 并将迭代次数增加两个数量级,我会得到

三进制耗时 22404ms

正常耗时 21403ms

这些结果更接近我的预期,让我觉得世界上一切正常(如果不是我的代码的话。)

三元/条件运算符实际上稍微慢了一点。


this question 开始,我有一部分是answered

我在 x64 发布模式下编译此控制台应用程序,并启用优化,并在没有附加调试器的情况下从命令行运行它。

using System; 
using System.Diagnostics;

class Program
{
    static void Main()
    {
        var stopwatch = new Stopwatch();

        var ternary = Looper(10, Ternary);
        var normal = Looper(10, Normal);

        if (ternary != normal)            {
            throw new Exception();
        }

        stopwatch.Start();
        ternary = Looper(10000000, Ternary);
        stopWatch.Stop();
        Console.WriteLine(
            "Ternary took {0}ms", 
            stopwatch.ElapsedMilliseconds);

        stopwatch.Start();
        normal = Looper(10000000, Normal);
        stopWatch.Stop();
        Console.WriteLine(
            "Normal took {0}ms", 
            stopwatch.ElapsedMilliseconds);

        if (ternary != normal)            {
            throw new Exception();
        }

        Console.ReadKey();
    }

    static int Looper(int iterations, Func<bool, int, int> operation)
    {
        var result = 0;
        for (int i = 0; i < iterations; i++)
        {
            var condition = result % 11 == 4;
            var value = ((i * 11) / 3) % 5;
            result = operation(condition, value);
        }

        return result;
    }

    static int Ternary(bool condition, in value)
    {
        return value + (condition ? 2 : 1);
    }

    static int Normal(int iterations)
    {
        if (condition)
        {
            return = 2 + value;
        }
        
        return = 1 + value;
    }
}

我没有遇到任何异常,控制台的输出也很接近,

三进制耗时 107 毫秒

正常耗时 230ms

当我分解两个逻辑函数的 CIL 时,我得到了这个,

... Ternary ...
{
     : ldarg.1      // push second arg
     : ldarg.0      // push first arg
     : brtrue.s T   // if first arg is true jump to T
     : ldc.i4.1     // push int32(1)
     : br.s F       // jump to F
    T: ldc.i4.2     // push int32(2)
    F: add          // add either 1 or 2 to second arg
     : ret          // return result
}

... Normal ...
{
     : ldarg.0      // push first arg
     : brfalse.s F  // if first arg is false jump to F
     : ldc.i4.2     // push int32(2)
     : ldarg.1      // push second arg
     : add          // add second arg to 2
     : ret          // return result
    F: ldc.i4.1     // push int32(1)
     : ldarg.1      // push second arg
     : add          // add second arg to 1
     : ret          // return result
}

虽然Ternary CIL 有点短,但在我看来,通过 CIL 执行任一函数的路径需要 3 次加载和 1 或 2 次跳转和返回。为什么Ternary 函数看起来快了一倍。

我知道,在实践中,它们都非常快,而且确实足够快,但我想了解其中的差异。

【问题讨论】:

  • 您不会在两次运行之间重置秒表。我个人从未使用过该类,但文档表明 Start() 方法将 resume 计时。我认为差异没有你想象的那么大。尝试将第二个 Start() 调用切换到 Restart()
  • 您还在测量调用每个Funcs 的时间吗?这在这里如何发挥作用?
  • @RoddyoftheFrozenPeas,你是对的。
  • 看别人的答案:回答我自己的问题,方法不同没关系。

标签: c# stopwatch


【解决方案1】:

两者花费的时间几乎完全相同。

您的结果不正确是因为您没有正确使用 Stopwatch。 “正常”的测量包括两个循环器所用的时间。

如果你改变第二个

stopwatch.Start();

stopwatch.Restart();

那么你就会得到正确的结果。


顺便说一下,为了得到更公平的比较,您可能应该执行

    return (condition ? value + 2 : value + 1);

而不是

    return value + (condition ? 2 : 1);

使其完全等同于其他功能。否则,您测量的不仅仅是条件运算符。

【讨论】:

  • 然而,我们仍然可以理解在原始问题中是如何衡量这一点的......
  • @HenkHolterman 必须以一致的方式对代码进行基准测试。没有这个,你怎么可能希望优化?
  • @RobbieDee - 没有人说这很容易,有用的基准测试非常困难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-30
  • 2015-12-23
  • 2020-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-22
相关资源
最近更新 更多