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

为什么会这样?


当前回答

查看生成的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和三元没有太大关系。

看看这些分解(我不会在这里重复,请参阅@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] 

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

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

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

查看生成的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(使预读更少)

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