Welches If-Konstrukt ist schneller - Anweisung oder ternärer Operator?

83

ifIn Java gibt es zwei Arten von Anweisungen: classic: if {} else {}und shorthand : exp ? value1 : value2. Ist einer schneller als der andere oder sind sie gleich?

Aussage:

int x;
if (expression) {
  x = 1;
} else {
  x = 2;
}

ternärer Operator:

int x = (expression) ? 1 : 2;
Rogach
quelle
34
Ich vermute, es gibt absolut keinen Unterschied. Es ist nur Syntax. Es sei denn, Compiler sind etwas böse (oder etwas anderes) und ich
liege
4
Haben Sie es (mikro) bewertet? Teilen Sie die Ergebnisse.
BalusC
3
Beide werden jit'ed bekommen. Es wird überhaupt keinen Unterschied geben. Und mach dir nicht die Mühe, das Zeug zu dekompilieren. Als erstes nimmt HotSpot alle Optimierungen vor, die von javac angewendet wurden.
Ivo Wetzel
11
Sie existieren nicht für unterschiedliche Geschwindigkeiten. Sie existieren für verschiedene Zwecke. Ich bin sicher, Sie verstehen den Unterschied zwischen Aussagen und Ausdrücken. Anweisungen führen Aktionen aus. Ausdrücke erzeugen Werte. ifist zur Verwendung in Anweisungen. ?ist zur Verwendung in Ausdrücken.
Mike Dunlavey
3
+1, da die Antworten auf diese Frage lesenswert sind, auch wenn die Absicht der ursprünglichen Frage falsch ist.
Jball

Antworten:

106

Es gibt dort nur eine Art von "if" -Anweisung. Der andere ist ein bedingter Ausdruck. Was die bessere Leistung betrifft: Sie könnten mit demselben Bytecode kompiliert werden, und ich würde erwarten, dass sie sich identisch verhalten - oder so nah, dass Sie in Bezug auf die Leistung definitiv nicht übereinander wählen möchten.

Manchmal ist eine ifAnweisung besser lesbar, manchmal ist der bedingte Operator besser lesbar. Insbesondere würde ich mit dem Bedingungsoperator empfehlen , wenn die beiden Operanden sind einfache und nebenwirkungsfrei, während , wenn der Hauptzweck der beiden Zweige sind ihre Nebenwirkungen, ich wahrscheinlich eine verwenden würde ifAussage.

Hier ist ein Beispielprogramm und ein Bytecode:

public class Test {
    public static void main(String[] args) {
        int x;
        if (args.length > 0) {
            x = 1;
        } else {
            x = 2;
        }
    }

    public static void main2(String[] args) {
        int x = (args.length > 0) ? 1 : 2;
    }
}

Bytecode dekompiliert mit javap -c Test:

public class Test extends java.lang.Object {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1
       4: return

  public static void main(java.lang.String[]
    Code:
       0: aload_0
       1: arraylength
       2: ifle          10
       5: iconst_1
       6: istore_1
       7: goto          12
      10: iconst_2
      11: istore_1
      12: return

  public static void main2(java.lang.String[
    Code:
       0: aload_0
       1: arraylength
       2: ifle          9
       5: iconst_1
       6: goto          10
       9: iconst_2
      10: istore_1
      11: return
}

Wie Sie sehen können, gibt es hier einen kleinen Unterschied im Bytecode - ob der istore_1innerhalb der Brance auftritt oder nicht (im Gegensatz zu meinem vorherigen äußerst fehlerhaften Versuch :), aber ich wäre sehr überrascht, wenn der JITter einen anderen nativen Code hätte.

Jon Skeet
quelle
s / bedingte Aussage / bedingter Ausdruck /
Laurence Gonsalves
1
Ich vermute, Sie wollten nicht beides mainund main2genau gleich sein?
ColinD
beeindruckend. Ich wusste bis jetzt nicht, dass Sie Bytecode kompilieren können.
Kyle
2
@Kyle: Ich habe Java kompiliert und dann mit Javap dekompiliert.
Jon Skeet
1
@ Kyle: Genau. Ich hatte meistens erwartet, dass der Bytecode identisch ist . Wie es ist, ist es fast identisch :)
Jon Skeet
10

Ihre beiden Beispiele werden wahrscheinlich zu identischem oder nahezu identischem Bytecode kompiliert, sodass es keinen Unterschied in der Leistung geben sollte.

Hätte es einen Unterschied in der Ausführungsgeschwindigkeit gegeben, sollten Sie immer noch die idiomatischste Version verwenden (die zweite zum Zuweisen einer einzelnen Variablen basierend auf einer einfachen Bedingung und zwei einfachen Unterausdrücken und die erste zum Ausführen komplexerer Operationen oder Operationen, die nicht in eine einzelne Zeile passen).

Victor Nicollet
quelle
8

Das sind die gleichen. Beide sind ziemlich schnell, typischerweise um 10-30 Nano-Sekunden. (abhängig vom Nutzungsmuster) Ist dieser Zeitrahmen für Sie wichtig?

Sie sollten das tun, was Sie für am klarsten halten.

Peter Lawrey
quelle
4

Nur um alle anderen Antworten zu ergänzen:

Der zweite Ausdruck wird oft als tertiärer / ternärer Operator / Anweisung bezeichnet. Dies kann sehr nützlich sein, da es einen Ausdruck zurückgibt. Manchmal wird der Code für typische kurze Anweisungen klarer.

Secko
quelle
4
Ein gutes Beispiel dafür in der Praxis: Wenn ich in Java einen String final basierend auf dem Ergebnis eines Ausdrucks erstellen muss, kann ich die ternäre Syntax final String verwenden. WhichTable = (Integer.parseInt (clientId)> 500)? "serverClients": "offlineClients"; Dann kann ich den Wert des Ausdrucks an Stellen verwenden, an denen whichTable endgültig sein muss. Folgendes wäre unzulässig: final String whichTable = ""; if (Integer.parseInt (clientId)> 500) {whichTable = "serverClients"; } else {whichTable = "offlineClients"; }
James Perih
@JamesPerih Im Fall eines finalFeldes können Sie Konstruktorblöcke verwenden, um einen Wert festzulegen (obwohl der bedingte Operator IMO milliardenfach besser aussieht), und mit lokalen Variablen können Sie einen Wert zuweisen, bevor Sie ihn später im Codeblock verwenden Ich denke, der einzige Fall, in dem ein Ternär einen Vorteil gegenüber dem Vorteil eines if-elseAufrufs super(...)oder this(...)eines Konstruktors bietet .
Kröw
3

auch nicht - sie werden gleich kompiliert.

Freddie
quelle
0

Der ternäre Operator ist schneller als die if-else-Bedingung.

public class TerinaryTest {
    public static void main(String[] args)
    {
        int j = 2,i = 0;
        Date d1 = new Date();
        for(long l=1;l<100000000;l++)
            if(i==1) j=1;
                else j=0;
        Date d2 = new Date();
        for(long l=1;l<100000000;l++)
            j=i==1?1:0;
        Date d3 = new Date();
        System.out.println("Time for if-else: " + (d2.getTime()-d1.getTime()));
        System.out.println("Time for ternary: " + (d3.getTime()-d2.getTime()));
    }
}

Testergebnisse:

Spur 1:

Zeit für if-else: 63

Zeit für ternäre: 31

Trail-2:

Zeit für if-else: 78

Zeit für ternäre: 47

Trail-3:

Zeit für if-else: 94

Zeit für ternäre: 31

Trail-4:

Zeit für if-else: 78

Zeit für ternäre: 47

rmkyjv
quelle
Ich hatte genau die entgegengesetzten Ergebnisse, als ich Ihr Beispiel ausführte, was zeigt, dass die Ergebnisse unzuverlässig sind. Leider geraten Sie in eine Mikrobenchmarkierungsfalle - es ist bekanntermaßen schwierig, Mikrobenchmarkierungen korrekt durchzuführen. Einige
Rogach
Ihr spezielles Beispiel weist mindestens die folgenden Probleme auf: 4 Versuche sind nirgends genug, Sie führen die Tests immer in der gleichen Reihenfolge aus (erster if-else, zweiter ternärer), Sie wärmen die JVM nicht auf, bevor Sie die Tests ausführen usw.
Rogach