Ich lese überall , dass ternäre Operator soll schneller sein als oder zumindest gleich wie, sein Äquivalent 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 kommentieren Sie die if
- else
Brocken, und Kommentar- der ternäre Operator Linie, wird es etwa 157 ms dauern.
Warum passiert dies?
c#
performance
conditional-operator
user1032613
quelle
quelle
DateTime
zur Messung der Leistung verwenden. Verwenden SieStopwatch
. Als nächstes etwas länger - das ist eine sehr kurze Zeit zum Messen.Random
Objekts einen Startwert, damit er immer dieselbe Reihenfolge aufweist. Wenn Sie unterschiedlichen Code mit unterschiedlichen Daten testen, können Sie sehr gut Leistungsunterschiede feststellen.Antworten:
Um diese Frage zu beantworten, untersuchen wir den Assembler-Code, der von den JITs X86 und X64 für jeden dieser Fälle erstellt wurde.
X86, wenn / dann
X86, ternär
X64, wenn / dann
X64, ternär
Erstens: Warum ist der X86-Code so viel langsamer als X64?
Dies ist auf die folgenden Merkmale des Codes zurückzuführen:
i
aus dem Array, während die X86-JIT mehrere Stapeloperationen (Speicherzugriff) in die Schleife einfügt.value
ist eine 64-Bit-Ganzzahl, für die auf X86 (add
gefolgt vonadc
) 2 Maschinenbefehle erforderlich sind, auf X64 (add
) jedoch nur 1 .Zweitens: Warum ist der ternäre Operator sowohl auf X86 als auch auf X64 langsamer?
Dies ist auf einen geringfügigen Unterschied in der Reihenfolge der Operationen zurückzuführen, der sich auf den Optimierer der JIT auswirkt. JIT des ternären Operators, anstatt direkt zu codieren
2
und3
in denadd
Maschinenanweisungen selbst zu , erstellt die JIT eine Zwischenvariable (in einem Register), um das Ergebnis zu speichern. Dieses Register wird dann vor dem Hinzufügen von 32 Bit auf 64 Bit vorzeichenerweitertvalue
. Da all dies in Registern für X64 ausgeführt wird, wird die Nettoauswirkung trotz der signifikanten Zunahme der Komplexität für den ternären Operator etwas minimiert.Die X86-JIT ist dagegen stärker betroffen, da durch 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 die Zugriffe) bis
[ebp-14h]
im ternären X86-Code).quelle
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 er ist viel kleiner als Sie sehen.
Ich habe die folgenden möglichen Änderungen vorgenommen:
/o+ /debug-
und führen Sie es außerhalb des Debuggers ausStopwatch
Ergebnisse mit
/platform:x64
(ohne die "Ignorieren" -Zeilen):Ergebnisse mit
/platform:x86
(ohne die "Ignorieren" -Zeilen):Meine Systemdetails:
Also im Gegensatz zu vorher, ich glaube , Sie werden einen echten Unterschied zu sehen - und es ist alles mit dem x86 - JIT zu tun. Ich möchte nicht genau was sagen den Unterschied verursacht - ich kann den Beitrag später mit weiteren Details aktualisieren, wenn ich mich die Mühe machen kann, auf cordbg zu gehen :)
Interessanterweise habe ich, ohne das Array zuerst zu sortieren, Tests, die mindestens 4,5x so lange dauern, zumindest auf x64. Ich vermute, dass dies mit der Verzweigungsvorhersage zu tun hat.
Code:
quelle
Der Unterschied hat wirklich nicht viel damit zu tun, ob / sonst gegen ternär.
Wenn man sich die zerlegten Zerlegungen ansieht (ich werde sie hier nicht wiederholen, siehe Antwort von @ 280Z28), stellt sich heraus, dass man Äpfel und Orangen vergleicht . In einem Fall erstellen Sie zwei verschiedene
+=
Operationen mit konstanten Werten. Welche Sie auswählen, hängt von einer Bedingung ab. In dem anderen Fall erstellen Sie eine Stelle,+=
an der der Wert hinzugefügt werden soll von einer Bedingung abhängt.Wenn Sie wirklich vergleichen möchten, ob / else mit ternär ist, wäre dies ein fairer Vergleich (jetzt sind beide gleich "langsam", oder wir könnten sogar sagen, dass ternär etwas schneller ist):
vs.
Nun wird die Demontage für das
if/else
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.quelle
diff
wurde eingeführt , aber ternär ist immer noch viel langsamer - überhaupt nicht das, was Sie gesagt haben. Haben Sie das Experiment durchgeführt, bevor Sie diese "Antwort" veröffentlicht haben?Bearbeiten:
Es wurde ein Beispiel hinzugefügt, das mit der if-else-Anweisung, jedoch nicht mit dem bedingten Operator ausgeführt werden kann.
Schauen Sie sich vor der Antwort bitte um [ Was ist schneller? ] auf Mr. Lipperts Blog. Und ich denke, die Antwort von Herrn Ersönmez ist hier die richtigste.
Ich versuche etwas zu erwähnen, das wir bei einer Programmiersprache auf hohem Niveau berücksichtigen sollten.
Zunächst einmal habe ich noch nie gehört, dass der bedingte Operator schneller sein soll oder die gleiche Leistung wie die if-else-Anweisung in C♯ .
Der Grund ist einfach: Was ist, wenn mit der if-else-Anweisung keine Operation ausgeführt wird:
Die Anforderung des bedingten Operators besteht darin, dass auf beiden Seiten ein Wert vorhanden sein muss , und in C♯ muss auch beide Seiten
:
den gleichen Typ haben. 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 sich die Leistung unterscheidet.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 etwas anderes.
Ein Wert ist nicht unbedingt mit der if-else-Anweisung verbunden. Ihre Annahme ist nur mit Optimierung möglich.
Ein weiteres Beispiel, um den Unterschied zwischen ihnen zu demonstrieren, wäre wie folgt:
Der obige Code wird jedoch kompiliert. Ersetzen Sie die if-else-Anweisung durch den bedingten Operator.
Der bedingte Operator und die if-else-Anweisungen sind konzeptionell gleich, wenn Sie dasselbe tun. Mit dem bedingten Operator in C ist dies möglicherweise sogar noch schneller , da C näher an der Baugruppe der Plattform liegt.
Für den ursprünglichen Code, den Sie angegeben haben, wird der bedingte Operator in einer foreach-Schleife verwendet, die die Dinge durcheinander bringt, um den Unterschied zwischen ihnen zu erkennen. Also schlage ich folgenden Code vor:
und das Folgende sind zwei Versionen der IL von optimiert und nicht. Da sie lang sind, verwende ich ein Bild, um zu zeigen, dass die rechte Seite die optimierte ist:
In beiden Codeversionen sieht die IL des bedingten Operators kürzer aus als die if-else-Anweisung, und es besteht immer noch ein Zweifel daran, dass der Maschinencode endgültig generiert wurde. Das Folgende sind die Anweisungen beider Methoden, und das erstere Bild ist nicht optimiert, das letztere ist das optimierte:
Nicht optimierte Anweisungen: (Klicken Sie hier, um das Bild in voller Größe anzuzeigen.)
Optimierte Anweisungen: (Klicken Sie hier, um das Bild in voller Größe anzuzeigen.)
In letzterem ist der gelbe Block der Code, der nur ausgeführt wird, wenn
i<=0
, und der blaue Block ist, wenni>0
. In beiden Versionen von Anweisungen ist die if-else-Anweisung kürzer.Beachten Sie, dass für verschiedene Anweisungen der [ CPI ] nicht unbedingt gleich ist. Für den identischen Befehl kosten logischerweise mehr Befehle einen längeren Zyklus. Wenn jedoch auch die Abrufzeit des Befehls und die Pipe / der Cache berücksichtigt wurden, hängt die tatsächliche Gesamtausführungszeit vom Prozessor ab. Der Prozessor kann auch die Zweige vorhersagen.
Moderne Prozessoren haben noch mehr Kerne, damit können die Dinge komplexer werden. Wenn Sie ein Intel-Prozessorbenutzer sind, sollten Sie sich [ Referenzhandbuch zur Optimierung von Intel® 64- und IA-32-Architekturen ] ansehen .
Ich weiß nicht, ob es eine Hardware-implementierte CLR gab, aber wenn ja, werden Sie mit dem bedingten Operator wahrscheinlich schneller, weil die IL offensichtlich geringer ist.
Hinweis: Der gesamte Maschinencode ist x86.
quelle
Ich habe das getan, was Jon Skeet getan hat, und habe 1 Iteration und 1.000 Iterationen durchlaufen und sowohl von OP als auch von Jon ein anderes Ergebnis erhalten. Bei mir ist der Ternär nur etwas schneller. Unten ist der genaue Code:
Die Ausgabe von meinem Programm:
Ein weiterer Lauf in Millisekunden:
Dies läuft in 64-Bit-XP, und ich lief ohne Debugging.
Bearbeiten - Wird in x86 ausgeführt:
Es gibt einen großen Unterschied bei der Verwendung von x86. Dies geschah ohne Debugging auf und auf demselben xp 64-Bit-Computer wie zuvor, wurde jedoch für x86-CPUs entwickelt. Das sieht eher nach OPs aus.
quelle
Der generierte Assembler-Code erzählt die Geschichte:
Erzeugt:
Wohingegen:
Erzeugt:
Das Ternär kann also 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 denselben Code wie ein if / else, zum Beispiel:
Erzeugt:
Welches ist das gleiche wie das if / else.
quelle
Ohne Debugging Strg + F5 ausführen, scheint der Debugger sowohl ifs als auch ternary erheblich zu verlangsamen, aber es scheint, dass er den ternären Operator viel mehr verlangsamt.
Wenn ich den folgenden Code ausführe, sind hier meine Ergebnisse. Ich denke, der kleine Millisekundenunterschied wird dadurch verursacht, dass der Compiler max = max optimiert und entfernt, aber diese Optimierung wahrscheinlich nicht für den ternären Operator vornimmt. Wenn jemand die Baugruppe überprüfen und bestätigen könnte, wäre das großartig.
Code
quelle
Betrachtet man die generierte IL, so sind darin 16 Operationen weniger als in der if / else-Anweisung (Kopieren und Einfügen des Codes von @ JonSkeet). Dies bedeutet jedoch nicht, dass es ein schnellerer Prozess sein sollte!
Um die Unterschiede in IL zusammenzufassen, entspricht die if / else-Methode fast dem Lesen des C # -Codes (Durchführen der Addition innerhalb des Zweigs), während der bedingte Code entweder 2 oder 3 auf den Stapel lädt (abhängig vom Wert) und addiert es dann zu einem Wert außerhalb der Bedingung.
Der andere Unterschied ist die verwendete Verzweigungsanweisung. Die if / else-Methode verwendet einen brtrue (branch if true), um über die erste Bedingung zu springen, und einen bedingungslosen Zweig, 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 (nachdem ich gerade über die Verzweigungsvorhersage gelesen habe) kann es zu einer Leistungseinbuße kommen, wenn die Verzweigung kleiner ist. Der bedingte Zweig hat nur 1 Befehl innerhalb des Zweigs, der if / else hat jedoch 7. Dies würde auch erklären, warum es einen Unterschied zwischen der Verwendung von long und int gibt, da das Ändern eines int die Anzahl der Befehle in den if / else-Zweigen um 1 verringert (macht das Vorlesen weniger)
quelle
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 verringert:
quelle
Zu viele gute Antworten, aber ich fand etwas Interessantes, sehr einfache Änderungen wirken sich aus. Nachdem Sie die folgenden Änderungen vorgenommen haben, dauert es dieselbe Zeit, um if-else und den ternären Operator auszuführen.
anstatt unter die Zeile zu schreiben
Ich habe das benutzt,
Eine der folgenden Antworten erwähnt auch, dass es eine schlechte Art ist, einen ternären Operator zu schreiben.
Ich hoffe, dies wird Ihnen helfen, einen ternären Operator zu schreiben, anstatt zu überlegen, welcher besser ist.
Verschachtelter ternärer Operator: Ich habe einen verschachtelten ternären Operator gefunden und mehrere if else-Blöcke benötigen ebenfalls dieselbe Zeit für die Ausführung.
quelle