我到处都读到,三元运算符应该比它的等效if-else块快,或者至少和它一样快。

然而,我做了以下测试,发现事实并非如此:

Random r = new Random();
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
    array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);

long value = 0;
DateTime begin = DateTime.UtcNow;

foreach (int i in array)
{
    if (i > 0)
    {
        value += 2;
    }
    else
    {
        value += 3;
    }
    // if-else block above takes on average 85 ms

    // OR I can use a ternary operator:
    // value += i > 0 ? 2 : 3; // takes 157 ms
}
DateTime end = DateTime.UtcNow;
MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString());

我的电脑花了85毫秒来运行上面的代码。但是如果我注释掉if-else块,并取消注释三元操作符行,这将花费大约157毫秒。

为什么会这样?


当前回答

编辑:所有更改…见下文。

我不能在x64 CLR上重现你的结果,但我可以在x86上。在x64上,我可以看到条件操作符和if/else之间有一个很小的差异(小于10%),但它比您看到的要小得多。

我做了以下可能的更改:

在控制台应用程序中运行 使用/o+ /debug-构建,并在调试器外部运行 运行这两段代码一次以JIT它们,然后多次以获得更高的准确性 使用秒表

使用/platform:x64的结果(没有“忽略”行):

if/else with 1 iterations: 17ms
conditional with 1 iterations: 19ms
if/else with 1000 iterations: 17875ms
conditional with 1000 iterations: 19089ms

使用/platform:x86的结果(没有“ignore”行):

if/else with 1 iterations: 18ms
conditional with 1 iterations: 49ms
if/else with 1000 iterations: 17901ms
conditional with 1000 iterations: 47710ms

我的系统详细信息:

x64 i7-2720QM CPU @2.20GHz 64位Windows 8 net 4.5

因此,与以前不同的是,我认为您看到了真正的不同——这都与x86 JIT有关。我不想确切地说是什么导致了这种差异-如果我能麻烦进入cordbg,我可能会在后面更新更多的细节。

有趣的是,如果不先对数组排序,我最终的测试时间大约是原来的4.5倍,至少在x64上。我猜想这与分支预测有关。

代码:

using System;
using System.Diagnostics;

class Test
{
    static void Main()
    {
        Random r = new Random(0);
        int[] array = new int[20000000];
        for(int i = 0; i < array.Length; i++)
        {
            array[i] = r.Next(int.MinValue, int.MaxValue);
        }
        Array.Sort(array);
        // JIT everything...
        RunIfElse(array, 1);
        RunConditional(array, 1);
        // Now really time it
        RunIfElse(array, 1000);
        RunConditional(array, 1000);
    }

    static void RunIfElse(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        sw.Stop();
        Console.WriteLine("if/else with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }

    static void RunConditional(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        sw.Stop();
        Console.WriteLine("conditional with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }
}

其他回答

我做了乔恩·斯基特所做的,运行了1次迭代和1000次迭代,得到了来自OP和乔恩的不同结果。在我的例子中,三进制稍微快一点。下面是准确的代码:

static void runIfElse(int[] array, int iterations)
    {
        long value = 0;
        Stopwatch ifElse = new Stopwatch();
        ifElse.Start();
        for (int c = 0; c < iterations; c++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        ifElse.Stop();
        Console.WriteLine(String.Format("Elapsed time for If-Else: {0}", ifElse.Elapsed));
    }

    static void runTernary(int[] array, int iterations)
    {
        long value = 0;
        Stopwatch ternary = new Stopwatch();
        ternary.Start();
        for (int c = 0; c < iterations; c++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        ternary.Stop();


        Console.WriteLine(String.Format("Elapsed time for Ternary: {0}", ternary.Elapsed));
    }

    static void Main(string[] args)
    {
        Random r = new Random();
        int[] array = new int[20000000];
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = r.Next(int.MinValue, int.MaxValue);
        }
        Array.Sort(array);

        long value = 0;

        runIfElse(array, 1);
        runTernary(array, 1);
        runIfElse(array, 1000);
        runTernary(array, 1000);
        
        Console.ReadLine();
    }

从我的程序输出:

If-Else的运行时间:00:00:00.0140543 三元的运行时间:00:00:00.0136723 If-Else的运行时间:00:00:14.0167870 Ternary的运行时间:00:00:13.9418520

另一个以毫秒为单位的运行:

If-Else的运行时间:20 三元的运行时间:19 If-Else的运行时间:13854 三元的运行时间:13610

这是在64位XP中运行的,我在没有调试的情况下运行。

编辑-运行在x86:

使用x86有很大的不同。这个过程没有像以前一样在同一台xp 64位机器上进行调试,而是为x86 cpu构建的。这个看起来更像OP。

If-Else的运行时间:18 三元的运行时间:35 If-Else的运行时间:20512 三元的运行时间:32673

编辑:所有更改…见下文。

我不能在x64 CLR上重现你的结果,但我可以在x86上。在x64上,我可以看到条件操作符和if/else之间有一个很小的差异(小于10%),但它比您看到的要小得多。

我做了以下可能的更改:

在控制台应用程序中运行 使用/o+ /debug-构建,并在调试器外部运行 运行这两段代码一次以JIT它们,然后多次以获得更高的准确性 使用秒表

使用/platform:x64的结果(没有“忽略”行):

if/else with 1 iterations: 17ms
conditional with 1 iterations: 19ms
if/else with 1000 iterations: 17875ms
conditional with 1000 iterations: 19089ms

使用/platform:x86的结果(没有“ignore”行):

if/else with 1 iterations: 18ms
conditional with 1 iterations: 49ms
if/else with 1000 iterations: 17901ms
conditional with 1000 iterations: 47710ms

我的系统详细信息:

x64 i7-2720QM CPU @2.20GHz 64位Windows 8 net 4.5

因此,与以前不同的是,我认为您看到了真正的不同——这都与x86 JIT有关。我不想确切地说是什么导致了这种差异-如果我能麻烦进入cordbg,我可能会在后面更新更多的细节。

有趣的是,如果不先对数组排序,我最终的测试时间大约是原来的4.5倍,至少在x64上。我猜想这与分支预测有关。

代码:

using System;
using System.Diagnostics;

class Test
{
    static void Main()
    {
        Random r = new Random(0);
        int[] array = new int[20000000];
        for(int i = 0; i < array.Length; i++)
        {
            array[i] = r.Next(int.MinValue, int.MaxValue);
        }
        Array.Sort(array);
        // JIT everything...
        RunIfElse(array, 1);
        RunConditional(array, 1);
        // Now really time it
        RunIfElse(array, 1000);
        RunConditional(array, 1000);
    }

    static void RunIfElse(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        sw.Stop();
        Console.WriteLine("if/else with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }

    static void RunConditional(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        sw.Stop();
        Console.WriteLine("conditional with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }
}

在下面的代码中,if/else似乎大约比三元运算符快1.4倍。然而,我发现引入一个临时变量会使三元运算符的运行时间减少约1.4倍:

If/Else: 98毫秒 三元:141毫秒 三元与温度变量:100毫秒

using System;
using System.Diagnostics;

namespace ConsoleApplicationTestIfElseVsTernaryOperator
{
    class Program
    {
        static void Main(string[] args)
        {
            Random r = new Random(0);
            int[] array = new int[20000000];
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = r.Next(int.MinValue, int.MaxValue);
            }
            Array.Sort(array);
            long value;
            Stopwatch stopwatch = new Stopwatch();

            value = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
                // 98 ms
            }
            stopwatch.Stop();
            Console.WriteLine("If/Else: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            value = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                value += (i > 0) ? 2 : 3; 
                // 141 ms
            }

            stopwatch.Stop();
            Console.WriteLine("Ternary: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            value = 0;
            int tempVar = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                tempVar = (i > 0) ? 2 : 3;
                value += tempVar; 
                // 100ms
            }
            stopwatch.Stop();
            Console.WriteLine("Ternary with temp var: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            Console.ReadKey(true);
        }
    }
}

答案太多了,但我发现了一些有趣的东西,非常简单的改变就能产生影响。在进行以下更改后,执行if-else和三元运算符将花费相同的时间。

而不是写在线下

value +=  i > 0 ? 2 : 3;

我用了这个,

int a =  i > 0 ? 2 : 3;
value += a;

下面其中一个回答也提到了写三元运算符有什么不好的方法。

我希望这能帮助你写三元运算符,而不是思考哪个更好。

嵌套三元运算符: 我发现嵌套的三元运算符和多个if else块也将花费相同的时间来执行。

编辑:

增加了一个可以使用if-else语句但不能使用条件操作符的示例。


在回答之前,请先看一下[哪个更快?]在利伯特的博客上。我认为Ersönmez先生的答案是最正确的。

我试图提到一些我们应该记住的高级编程语言。

首先,我从未听说过条件运算符应该比c#中的if-else语句更快或性能相同。

原因很简单,如果没有if-else语句的操作:

if (i > 0)
{
    value += 2;
}
else
{
}

条件操作符的要求是:必须有一个值的任意边,并且在c#中,它还要求:的两端具有相同的类型。这只是使它不同于if-else语句。因此,你的问题变成了问指令的机器代码是如何产生的,从而导致性能的差异。

如果使用条件操作符,从语义上讲它是:

无论表达式的值是多少,都有一个值。

但是使用if-else语句:

如果表达式被赋值为true,执行一些操作;如果没有,做另一件事。

if-else语句不一定包含值。你的假设只有在优化时才有可能。

另一个例子可以说明它们之间的区别,如下所示:

var array1=new[] { 1, 2, 3 };
var array2=new[] { 5, 6, 7 };

if(i>0)
    array1[1]=4;
else
    array2[2]=4;

上面的代码编译,但是,用条件操作符替换if-else语句不会编译:

var array1=new[] { 1, 2, 3 };
var array2=new[] { 5, 6, 7 };
(i>0?array1[1]:array2[2])=4; // incorrect usage 

当你做同样的事情时,条件操作符和if-else语句在概念上是相同的,在C中使用条件操作符可能更快,因为C更接近平台的程序集。


对于您提供的原始代码,条件操作符在foreach循环中使用,这将使查看它们之间的区别变得混乱。所以我提议以下代码:

public static class TestClass {
    public static void TestConditionalOperator(int i) {
        long value=0;
        value+=i>0?2:3;
    }

    public static void TestIfElse(int i) {
        long value=0;

        if(i>0) {
            value+=2;
        }
        else {
            value+=3;
        }
    }

    public static void TestMethod() {
        TestConditionalOperator(0);
        TestIfElse(0);
    }
}

以下是优化和未优化的IL的两个版本。因为它们很长,我用一张图片来展示,右边是优化后的:

(点击查看全尺寸图片。)

在这两个版本的代码中,条件操作符的IL看起来都比if-else语句短,并且最终生成的机器代码仍然存在疑问。以下是两种方法的说明,前者为未优化图像,后者为优化图像:

非优化说明:(点击查看全尺寸图片) 优化说明:(点击查看全尺寸图片)

在后者中,黄色块是仅当i<=0时执行的代码,蓝色块是当i>0时执行的代码。在任何版本的指令中,if-else语句都较短。

请注意,对于不同的指令,[CPI]不一定相同。逻辑上,对于相同的指令,更多的指令需要更长的周期。但是如果将指令获取时间和管道/缓存也考虑在内,那么实际的总执行时间取决于处理器。处理器还可以预测分支。

现代处理器甚至有更多的核心,事情可能会更复杂。如果你是英特尔处理器的用户,你可能想看一下[英特尔®64和IA-32架构优化参考手册]。

我不知道是否有硬件实现的CLR,但如果有,使用条件操作符可能会更快,因为IL明显更少。

注:所有机器代码都是x86的。