it-swarm.com.de

Ternärer Operator ist doppelt so langsam wie ein if-else-Block?

Ich habe überall gelesen, dass der ternäre Operator schneller oder zumindest genauso schnell sein soll wie der entsprechende if-else -Block.

Ich habe jedoch den folgenden Test durchgeführt und festgestellt, dass dies nicht der Fall ist:

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());

Mein Computer brauchte 85 ms, um den obigen Code auszuführen. Aber wenn ich den Block if-else auskommentiere und die ternäre Operatorzeile auskommentiere, dauert es ungefähr 157 ms.

Warum passiert das?

243
user1032613

Um diese Frage zu beantworten, untersuchen wir den von den JITs X86 und X64 für jeden dieser Fälle erzeugten Assembly-Code.

X86, wenn/dann

    32:                 foreach (int i in array)
0000007c 33 D2                xor         edx,edx 
0000007e 83 7E 04 00          cmp         dword ptr [esi+4],0 
00000082 7E 1C                jle         000000A0 
00000084 8B 44 96 08          mov         eax,dword ptr [esi+edx*4+8] 
    33:                 {
    34:                     if (i > 0)
00000088 85 C0                test        eax,eax 
0000008a 7E 08                jle         00000094 
    35:                     {
    36:                         value += 2;
0000008c 83 C3 02             add         ebx,2 
0000008f 83 D7 00             adc         edi,0 
00000092 EB 06                jmp         0000009A 
    37:                     }
    38:                     else
    39:                     {
    40:                         value += 3;
00000094 83 C3 03             add         ebx,3 
00000097 83 D7 00             adc         edi,0 
0000009a 42                   inc         edx 
    32:                 foreach (int i in array)
0000009b 39 56 04             cmp         dword ptr [esi+4],edx 
0000009e 7F E4                jg          00000084 
    30:             for (int x = 0; x < iterations; x++)
000000a0 41                   inc         ecx 
000000a1 3B 4D F0             cmp         ecx,dword ptr [ebp-10h] 
000000a4 7C D6                jl          0000007C 

X86, ternär

    59:                 foreach (int i in array)
00000075 33 F6                xor         esi,esi 
00000077 83 7F 04 00          cmp         dword ptr [edi+4],0 
0000007b 7E 2D                jle         000000AA 
0000007d 8B 44 B7 08          mov         eax,dword ptr [edi+esi*4+8] 
    60:                 {
    61:                     value += i > 0 ? 2 : 3;
00000081 85 C0                test        eax,eax 
00000083 7F 07                jg          0000008C 
00000085 BA 03 00 00 00       mov         edx,3 
0000008a EB 05                jmp         00000091 
0000008c BA 02 00 00 00       mov         edx,2 
00000091 8B C3                mov         eax,ebx 
00000093 8B 4D EC             mov         ecx,dword ptr [ebp-14h] 
00000096 8B DA                mov         ebx,edx 
00000098 C1 FB 1F             sar         ebx,1Fh 
0000009b 03 C2                add         eax,edx 
0000009d 13 CB                adc         ecx,ebx 
0000009f 89 4D EC             mov         dword ptr [ebp-14h],ecx 
000000a2 8B D8                mov         ebx,eax 
000000a4 46                   inc         esi 
    59:                 foreach (int i in array)
000000a5 39 77 04             cmp         dword ptr [edi+4],esi 
000000a8 7F D3                jg          0000007D 
    57:             for (int x = 0; x < iterations; x++)
000000aa FF 45 E4             inc         dword ptr [ebp-1Ch] 
000000ad 8B 45 E4             mov         eax,dword ptr [ebp-1Ch] 
000000b0 3B 45 F0             cmp         eax,dword ptr [ebp-10h] 
000000b3 7C C0                jl          00000075 

X64, wenn/dann

    32:                 foreach (int i in array)
00000059 4C 8B 4F 08          mov         r9,qword ptr [rdi+8] 
0000005d 0F 1F 00             nop         dword ptr [rax] 
00000060 45 85 C9             test        r9d,r9d 
00000063 7E 2B                jle         0000000000000090 
00000065 33 D2                xor         edx,edx 
00000067 45 33 C0             xor         r8d,r8d 
0000006a 4C 8B 57 08          mov         r10,qword ptr [rdi+8] 
0000006e 66 90                xchg        ax,ax 
00000070 42 8B 44 07 10       mov         eax,dword ptr [rdi+r8+10h] 
    33:                 {
    34:                     if (i > 0)
00000075 85 C0                test        eax,eax 
00000077 7E 07                jle         0000000000000080 
    35:                     {
    36:                         value += 2;
00000079 48 83 C5 02          add         rbp,2 
0000007d EB 05                jmp         0000000000000084 
0000007f 90                   nop 
    37:                     }
    38:                     else
    39:                     {
    40:                         value += 3;
00000080 48 83 C5 03          add         rbp,3 
00000084 FF C2                inc         edx 
00000086 49 83 C0 04          add         r8,4 
    32:                 foreach (int i in array)
0000008a 41 3B D2             cmp         edx,r10d 
0000008d 7C E1                jl          0000000000000070 
0000008f 90                   nop 
    30:             for (int x = 0; x < iterations; x++)
00000090 FF C1                inc         ecx 
00000092 41 3B CC             cmp         ecx,r12d 
00000095 7C C9                jl          0000000000000060 

X64, ternär

    59:                 foreach (int i in array)
00000044 4C 8B 4F 08          mov         r9,qword ptr [rdi+8] 
00000048 45 85 C9             test        r9d,r9d 
0000004b 7E 2F                jle         000000000000007C 
0000004d 45 33 C0             xor         r8d,r8d 
00000050 33 D2                xor         edx,edx 
00000052 4C 8B 57 08          mov         r10,qword ptr [rdi+8] 
00000056 8B 44 17 10          mov         eax,dword ptr [rdi+rdx+10h] 
    60:                 {
    61:                     value += i > 0 ? 2 : 3;
0000005a 85 C0                test        eax,eax 
0000005c 7F 07                jg          0000000000000065 
0000005e B8 03 00 00 00       mov         eax,3 
00000063 EB 05                jmp         000000000000006A 
00000065 B8 02 00 00 00       mov         eax,2 
0000006a 48 63 C0             movsxd      rax,eax 
0000006d 4C 03 E0             add         r12,rax 
00000070 41 FF C0             inc         r8d 
00000073 48 83 C2 04          add         rdx,4 
    59:                 foreach (int i in array)
00000077 45 3B C2             cmp         r8d,r10d 
0000007a 7C DA                jl          0000000000000056 
    57:             for (int x = 0; x < iterations; x++)
0000007c FF C1                inc         ecx 
0000007e 3B CD                cmp         ecx,ebp 
00000080 7C C6                jl          0000000000000048 

Erstens: Warum ist der X86-Code so viel langsamer als X64?

Dies liegt an den folgenden Merkmalen des Codes:

  1. X64 verfügt über mehrere zusätzliche Register, und jedes Register ist 64-Bit. Dies ermöglicht dem X64-JIT, die innere Schleife vollständig unter Verwendung von Registern auszuführen, abgesehen vom Laden von i aus dem Array, während das X86-JIT mehrere Stapeloperationen (Speicherzugriff) in der Schleife platziert.
  2. value ist eine 64-Bit-Ganzzahl, für die 2 Computeranweisungen auf X86 (add gefolgt von adc), aber nur 1 auf X64 (add) erforderlich sind.

Zweitens: Warum ist der ternäre Operator auf X86 und X64 langsamer?

Dies ist auf einen geringfügigen Unterschied in der Reihenfolge der Vorgänge zurückzuführen, der sich auf den Optimierer der JIT auswirkt. Um den ternären Operator zu JITen, anstatt 2 Und 3 Direkt in den Maschinenbefehlen add zu codieren, erstellt der JIT eine Zwischenvariable (in einem Register), um das Ergebnis zu halten . Dieses Register wird dann von 32 Bit auf 64 Bit vorzeichenerweitert, bevor es zu value hinzugefügt wird. Da all dies in Registern für X64 ausgeführt wird, wird die Nettoauswirkung trotz der für den ternären Operator erheblich gestiegenen Komplexität etwas minimiert.

Die X86-JIT ist dagegen stärker betroffen, da durch das Hinzufügen eines neuen Zwischenwerts in der inneren Schleife ein weiterer Wert "verschüttet" wird, was zu mindestens 2 zusätzlichen Speicherzugriffen in der inneren Schleife führt (siehe Zugriffe) auf [ebp-14h] im ternären X86-Code).

373
Sam Harwell

EDIT: Alle Änderungen ... siehe unten.

Ich kann Ihre Ergebnisse auf der x64-CLR nicht reproduzieren, aber ich kann auf x86. Auf x64 sehe ich einen kleinen Unterschied (weniger als 10%) zwischen dem bedingten Operator und dem if/else, aber es ist viel kleiner als Sie sehen.

Ich habe die folgenden möglichen Änderungen vorgenommen:

  • Führen Sie eine Konsolen-App aus
  • Bauen mit /o+ /debug- und außerhalb des Debuggers ausführen
  • Führen Sie beide Codeteile einmal aus, um sie zu JITen, und dann viele Male, um die Genauigkeit zu erhöhen
  • Benutze Stopwatch

Ergebnisse mit /platform:x64 (ohne die "ignorieren" Zeilen):

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

Ergebnisse mit /platform:x86 (ohne die "ignorieren" Zeilen):

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

Meine Systemdetails:

  • x64 i7-2720QM-CPU bei 2,20 GHz
  • 64-Bit-Windows 8
  • .NET 4.5

Im Gegensatz zu früher, denke ich, sehen Sie sind einen echten Unterschied - und das hat alles mit dem x86-JIT zu tun. Ich möchte nicht genau sagen, dass was den Unterschied verursacht - ich kann den Beitrag später mit mehr Details aktualisieren, wenn ich mir die Mühe machen kann, auf cordbg zuzugreifen :)

Interessanterweise habe ich, ohne das Array zuerst zu sortieren, Tests, die mindestens auf x64 etwa 4,5-mal so lange dauern. Ich vermute, dass dies mit der Verzweigungsvorhersage zu tun hat.

Code:

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);
    }
}
63
Jon Skeet

Der Unterschied hat wirklich nicht viel mit if/else und ternary zu tun.

Betrachtet man die ausgelassenen Disassemblies (ich werde es hier nicht wiederholen, siehe die Antwort von @ 280Z28), stellt sich heraus, dass man Äpfel und Orangen vergleicht. In einem Fall erstellen Sie zwei verschiedene += Operationen mit konstanten Werten und welche Sie auswählen, hängt von einer Bedingung ab, und in dem anderen Fall erstellen Sie ein += wobei der zu addierende Wert von einer Bedingung abhängt.

Wenn Sie wirklich if/else mit ternary vergleichen möchten, wäre dies ein fairerer Vergleich (jetzt sind beide gleichermaßen "langsam", oder wir könnten sogar sagen, dass ternary etwas schneller ist):

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

vs.

value += i > 0 ? 2 : 3;

Nun die Demontage für die if/else wird wie unten gezeigt. Beachten Sie, dass dies etwas schlimmer ist als der ternäre Fall, da die Verwendung der Register für die Schleifenvariable (i) ebenfalls beendet wird.

                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] 
43
Eren Ersönmez

Bearbeiten:

Es wurde ein Beispiel hinzugefügt, das mit der if-else-Anweisung aber nicht mit dem bedingten Operator durchgeführt werden kann.


Schauen Sie sich vor der Antwort bitte [ Was ist schneller? ] in Herrn Lipperts Blog an. Und ich denke die Antwort von Herrn Ersönmez ist hier die richtigste.

Ich versuche, etwas zu erwähnen, das wir bei einer höheren Programmiersprache beachten sollten.

Zuallererst habe ich noch nie gehört, dass der bedingte Operator schneller oder genauso performant sein soll wie die if-else-Anweisung in C♯ .

Der Grund ist einfach, dass was ist, wenn es keine Operation mit der if-else-Anweisung gibt:

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

Die Bedingung des bedingten Operators ist , dass es für jede Seite einen Wert geben muss, und in C♯ ist es auch erforderlich, dass beide Seiten von : hat den gleichen Typ. Dies unterscheidet es nur von der if-else-Anweisung. So wird Ihre Frage zu einer Frage, wie die Anweisung des Maschinencodes erzeugt wird, so dass der Leistungsunterschied entsteht.

Mit dem bedingten Operator ist es semantisch:

Unabhängig davon, welcher Ausdruck ausgewertet wird, gibt es einen Wert.

Aber mit if-else-Anweisung:

Wenn der Ausdruck als wahr ausgewertet wird, tun Sie etwas; Wenn nicht, machen Sie eine andere Sache.

Ein Wert ist nicht unbedingt mit der if-else-Anweisung verbunden. Ihre Annahme ist nur mit der Optimierung möglich.

Ein weiteres Beispiel, um den Unterschied zwischen ihnen zu demonstrieren, sieht folgendermaßen aus:

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

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

der obige Code wird kompiliert. Ersetzen Sie jedoch die if-else-Anweisung durch den bedingten Operator.

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

Der bedingte Operator und die if-else-Anweisungen sind konzeptionell gleich, wenn Sie dasselbe tun, möglicherweise sogar schneller mit dem bedingten Operator in C , da C ist näher an der Versammlung der Plattform.


Für den von Ihnen angegebenen Originalcode wird der Bedingungsoperator in einer foreach-Schleife verwendet, die die Dinge durcheinander bringt, um den Unterschied zwischen ihnen zu erkennen. Also schlage ich folgenden Code vor:

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);
    }
}

und das Folgende sind zwei Versionen der IL von optimiert und nicht. Da sie lang sind, verwende ich ein Bild, um zu zeigen, die rechte Seite ist die optimierte:

(Klicken, um das Bild in voller Größe zu sehen.) hSN6s.png

In beiden Codeversionen sieht die IL des bedingten Operators kürzer aus als die if-else-Anweisung, und es besteht immer noch ein Zweifel an dem endgültig generierten Maschinencode. Im Folgenden sind die Anweisungen beider Methoden aufgeführt, und das erste Bild ist nicht optimiert, das zweite ist das optimierte:

  • Nicht optimierte Anweisungen: (Klicken, um das Bild in voller Größe zu sehen.) ybhgM.png

  • Optimierte Anleitung: (Zum Vergrößern anklicken.) 6kgzJ.png

In letzterem Fall ist der gelbe Block der Code, der nur ausgeführt wird, wenn i<=0, und der blaue Block ist, wenn i>0. In beiden Versionen von Anweisungen ist die if-else-Anweisung kürzer.

Beachten Sie, dass für verschiedene Anweisungen das [ CPI ] nicht unbedingt dasselbe ist. Logischerweise kosten bei identischen Befehlen mehr Befehle einen längeren Zyklus. Berücksichtigt man aber auch die Befehlsabrufzeit und Pipe/Cache, so hängt die tatsächliche Gesamtausführungszeit vom Prozessor ab. Der Prozessor kann auch die Verzweigungen vorhersagen.

Moderne Prozessoren haben noch mehr Kerne, damit können die Dinge komplexer werden. Wenn Sie ein Intel-Prozessor-Benutzer waren, möchten Sie vielleicht einen Blick auf [ Referenzhandbuch zur Optimierung der Intel® 64- und IA-32-Architekturen ] werfen.

Ich weiß nicht, ob es eine hardwareimplementierte CLR gab, aber wenn ja, werden Sie wahrscheinlich mit dem bedingten Operator schneller, weil die IL offensichtlich geringer ist.

Hinweis: Der gesamte Maschinencode ist x86.

9
Ken Kin

Ich habe das getan, was Jon Skeet getan hat, und habe 1 Iteration und 1.000 Iterationen durchlaufen und ein anderes Ergebnis von OP und Jon erhalten. Bei mir ist der Ternär nur etwas schneller. Unten ist der genaue Code:

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();
    }

Die Ausgabe von meinem Programm:

Verstrichene Zeit für If-Else: 00: 00: 00.0140543

Verstrichene Zeit für Ternary: 00: 00: 00.0136723

Verstrichene Zeit für If-Else: 00: 00: 14.0167870

Verstrichene Zeit für Ternary: 00: 00: 13.9418520

Ein weiterer Lauf in Millisekunden:

Verstrichene Zeit für If-Else: 20

Verstrichene Zeit für Ternary: 19

Verstrichene Zeit für If-Else: 13854

Verstrichene Zeit für Ternary: 13610

Dies läuft unter 64-Bit XP und ich lief ohne Debugging.

Bearbeiten - Läuft in x86:

Bei x86 gibt es einen großen Unterschied. Dies wurde ohne Debugging auf und auf demselben XP 64-Bit-Computer wie zuvor durchgeführt, jedoch für x86-CPUs entwickelt. Das sieht eher nach OP aus.

Verstrichene Zeit für If-Else: 18

Verstrichene Zeit für Ternary: 35

Verstrichene Zeit für If-Else: 20512

Verstrichene Zeit für Ternary: 32673

7
Shaz

Der erzeugte Assembler-Code erzählt die Geschichte:

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

Erzeugt:

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

Wohingegen:

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

Erzeugt:

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:

Das ternäre kann kürzer und schneller sein, einfach weil weniger Anweisungen und keine Sprünge verwendet werden wenn Sie nach wahr/falsch suchen. Wenn Sie andere Werte als 1 und 0 verwenden, erhalten Sie den gleichen Code wie if/else, zum Beispiel:

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

Erzeugt:

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:

Welches ist das gleiche wie das if/else.

5
user2513931

In der generierten AWL sind 16 Operationen weniger enthalten als in der if/else-Anweisung (Kopieren und Einfügen von @ JonSkeet-Code). Das bedeutet jedoch nicht, dass es ein schnellerer Prozess sein sollte!

Um die Unterschiede in IL zusammenzufassen, wird die if/else-Methode in etwa so übersetzt, wie der C # -Code liest (der die Addition innerhalb des Zweigs ausführt), während der bedingte Code entweder 2 oder 3 in den Stapel lädt (je nach Wert) und Fügt es dann zu einem Wert außerhalb der Bedingung hinzu.

Der andere Unterschied ist die verwendete Verzweigungsanweisung. Die if/else-Methode verwendet eine brtrue (branch if true), um die erste Bedingung zu überspringen, und eine unbedingte Verzweigung, um von der ersten aus der if-Anweisung zu springen. Der bedingte Code verwendet ein bgt (Verzweigung, wenn größer als) anstelle eines brtrue, was möglicherweise ein langsamerer Vergleich sein könnte.

Außerdem kann es (nachdem Sie gerade etwas über die Verzweigungsvorhersage gelesen haben) zu Performanceeinbußen kommen, wenn die Verzweigung kleiner ist. Die bedingte Verzweigung hat nur 1 Anweisung innerhalb der Verzweigung, aber die if/else-Anweisung hat 7. Dies würde auch erklären, warum es einen Unterschied zwischen der Verwendung von long und int gibt, da das Ändern in eine int die Anzahl der Anweisungen in der if/else-Verzweigung um 1 verringert (weniger Vorauslesen)

4

Wenn Sie das Programm ohne Debuggen von Strg + F5 ausführen, verlangsamt der Debugger sowohl ifs als auch ternary erheblich, verlangsamt jedoch den ternary-Operator erheblich.

Wenn ich den folgenden Code hier ausführe, sind meine Ergebnisse. Ich denke, dass der kleine Millisekunden-Unterschied durch den Compiler verursacht wird, der das Maximum = Maximum optimiert und es entfernt, aber diese Optimierung wahrscheinlich nicht für den ternären Operator vornimmt. Wenn jemand die Versammlung überprüfen und bestätigen könnte, wäre das großartig.

--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("#.####"));
        }
4
CodeCamper

Im folgenden Code scheint if/else ungefähr 1,4-mal schneller zu sein als der ternäre Operator. Ich fand jedoch heraus, dass die Einführung einer temporären Variablen die Laufzeit des ternären Operators ungefähr um das 1,4-fache verkürzt:

If/Else: 98 ms

Ternär: 141 ms

Ternär mit temp var: 100 ms

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);
        }
    }
}
1
Alexey Novikov