我到处都读到,三元运算符应该比它的等效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毫秒。

为什么会这样?


当前回答

在下面的代码中,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);
        }
    }
}

其他回答

在没有调试ctrl+F5的情况下运行,调试器似乎使if和三元操作符都显着变慢,但似乎它使三元操作符变慢得更多。

当我运行以下代码时,这里是我的结果。我认为微小的毫秒差异是由编译器优化max=max并删除它造成的,但可能没有对三元操作符进行优化。如果有人能检查组装并确认这一点,那就太棒了。

--Run #1--
Type   | Milliseconds
Ternary 706
If     704
%: .9972
--Run #2--
Type   | Milliseconds
Ternary 707
If     704
%: .9958
--Run #3--
Type   | Milliseconds
Ternary 706
If     704
%: .9972

Code

  for (int t = 1; t != 10; t++)
        {
            var s = new System.Diagnostics.Stopwatch();
            var r = new Random(123456789);   //r
            int[] randomSet = new int[1000]; //a
            for (int i = 0; i < 1000; i++)   //n
                randomSet[i] = r.Next();     //dom
            long _ternary = 0; //store
            long _if = 0;      //time
            int max = 0; //result
            s.Start();
            for (int q = 0; q < 1000000; q++)
            {
                for (int i = 0; i < 1000; i++)
                    max = max > randomSet[i] ? max : randomSet[i];
            }
            s.Stop();
            _ternary = s.ElapsedMilliseconds;
            max = 0;
            s = new System.Diagnostics.Stopwatch();
            s.Start();
            for (int q = 0; q < 1000000; q++)
            {
                for (int i = 0; i < 1000; i++)
                    if (max > randomSet[i])
                        max = max; // I think the compiler may remove this but not for the ternary causing the speed difference.
                    else
                        max = randomSet[i];
            }

            s.Stop();
            _if = s.ElapsedMilliseconds;
            Console.WriteLine("--Run #" + t+"--");
            Console.WriteLine("Type   | Milliseconds\nTernary {0}\nIf     {1}\n%: {2}", _ternary, _if,((decimal)_if/(decimal)_ternary).ToString("#.####"));
        }

查看生成的IL,其中的操作比if/else语句少16个(复制和粘贴@JonSkeet的代码)。然而,这并不意味着它应该是一个更快的过程!

为了总结IL中的差异,if/else方法翻译成与c#代码读取几乎相同(在分支中执行添加),而条件代码将2或3加载到堆栈上(取决于值),然后将其添加到条件之外的值。

另一个区别是使用的分支指令。if/else方法使用一个brtrue(分支if true)来跳过第一个条件,使用一个无条件分支从if语句的第一个跳出。条件代码使用bgt(如果大于则使用分支)而不是brtrue,这可能是一个较慢的比较。

另外(已经阅读了关于分支预测的内容),较小的分支可能会导致性能损失。条件分支中只有1条指令,而if/else有7条。这也解释了为什么使用long和int之间有区别,因为更改为int将减少if/else分支中的指令数1(使预读更少)

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

而不是写在线下

value +=  i > 0 ? 2 : 3;

我用了这个,

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

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

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

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

生成的汇编代码会告诉你:

a = (b > c) ? 1 : 0;

生成:

mov  edx, DWORD PTR a[rip]
mov  eax, DWORD PTR b[rip]
cmp  edx, eax
setg al

而:

if (a > b) printf("a");
else printf("b");

生成:

mov edx, DWORD PTR a[rip]
mov eax, DWORD PTR b[rip]
cmp edx, eax
jle .L4
    ;printf a
jmp .L5
.L4:
    ;printf b
.L5:

因此,如果你在寻找真/假,由于使用更少的指令和没有跳转,三元可以更短更快。如果你使用的值不是1和0,你将得到与If /else相同的代码,例如:

a = (b > c) ? 2 : 3;

生成:

mov edx, DWORD PTR b[rip]
mov eax, DWORD PTR c[rip]
cmp edx, eax
jle .L6
    mov eax, 2
jmp .L7
.L6:
    mov eax, 3
.L7:

这和if/else一样。

我做了乔恩·斯基特所做的,运行了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