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

为什么会这样?


当前回答

在没有调试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("#.####"));
        }

其他回答

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

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

我不能在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);
    }
}

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

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一样。

这个区别与if/else和三元没有太大关系。

看看这些分解(我不会在这里重复,请参阅@280Z28的答案),结果是你在比较苹果和橘子。在一种情况下,创建两个具有常量值的不同+=操作,选择哪个操作取决于条件;在另一种情况下,创建+=,其中要添加的值取决于条件。

如果你想真正比较If /else和三元,这将是一个更公平的比较(现在两者都一样“慢”,或者我们甚至可以说三元更快一点):

int diff;
if (i > 0) 
    diff = 2;
else 
    diff = 3;
value += diff;

vs.

value += i > 0 ? 2 : 3;

现在,对if/else的分解如下所示。注意,这比三元情况更糟糕,因为它也停止使用循环变量(i)的寄存器。

                if (i > 0)
0000009d  cmp         dword ptr [ebp-20h],0 
000000a1  jle         000000AD 
                {
                    diff = 2;
000000a3  mov         dword ptr [ebp-24h],2 
000000aa  nop 
000000ab  jmp         000000B4 
                }
                else
                {
                    diff = 3;
000000ad  mov         dword ptr [ebp-24h],3 
                }
                value += diff;
000000b4  mov         eax,dword ptr [ebp-18h] 
000000b7  mov         edx,dword ptr [ebp-14h] 
000000ba  mov         ecx,dword ptr [ebp-24h] 
000000bd  mov         ebx,ecx 
000000bf  sar         ebx,1Fh 
000000c2  add         eax,ecx 
000000c4  adc         edx,ebx 
000000c6  mov         dword ptr [ebp-18h],eax 
000000c9  mov         dword ptr [ebp-14h],edx 
000000cc  inc         dword ptr [ebp-28h] 

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

而不是写在线下

value +=  i > 0 ? 2 : 3;

我用了这个,

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

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

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

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