Która konstrukcja „jeśli” jest szybsza - instrukcja czy operator trójskładnikowy?

83

Istnieją dwa rodzaje ifoświadczeń w Javie - Classic: if {} else {}i skrócone: exp ? value1 : value2. Czy jeden jest szybszy od drugiego, czy są takie same?

komunikat:

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

operator trójskładnikowy:

int x = (expression) ? 1 : 2;
Rogach
źródło
34
Zgaduję, że nie ma żadnej różnicy. To tylko składnia. Chyba że kompilatory są trochę złe (lub coś innego) i się mylę
sinelaw
4
Czy (mikro) przetestowałeś to? Podziel się wynikami.
BalusC,
3
Obaj będą w szoku. Nie będzie żadnej różnicy. I nie kłopocz się dekompilacją rzeczy. Pierwszą rzeczą, którą robi HotSpot, jest usunięcie wszystkich optymalizacji, które zostały zastosowane przez javac.
Ivo Wetzel
11
Nie istnieją dla różnych prędkości. Istnieją do różnych celów. Jestem pewien, że rozumiesz różnicę między stwierdzeniami a wyrażeniami. Wypowiedzi wykonują czynności. Wyrażenia tworzą wartości. ifjest do użytku w wyciągach. ?jest używany w wyrażeniach.
Mike Dunlavey
3
+1, ponieważ odpowiedzi na to pytanie są warte przeczytania, nawet jeśli intencja pierwotnego pytania jest błędna.
jball

Odpowiedzi:

106

Jest tam tylko jeden rodzaj instrukcji „jeśli”. Drugi to wyrażenie warunkowe. Co do tego, który będzie działał lepiej: mogą skompilować się do tego samego kodu bajtowego i spodziewałbym się, że będą się zachowywać identycznie - lub tak blisko, że zdecydowanie nie będziesz chciał wybierać jednego z nich pod względem wydajności.

Czasami ifinstrukcja będzie bardziej czytelna, czasami operator warunkowy będzie bardziej czytelny. W szczególności zalecałbym użycie operatora warunkowego, gdy dwa operandy są proste i wolne od skutków ubocznych, podczas gdy jeśli głównym celem tych dwóch gałęzi ich skutki uboczne, prawdopodobnie użyłbym ifinstrukcji.

Oto przykładowy program i kod bajtowy:

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

Dekompilacja kodu bajtowego za pomocą 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
}

Jak widać, jest tu niewielka różnica w istore_1kodzie bajtowym - czy występuje w brance, czy nie (w przeciwieństwie do mojej poprzedniej bardzo błędnej próby :) ale byłbym bardzo zaskoczony, gdyby JITter skończył z innym rodzimym kodem.

Jon Skeet
źródło
s / instrukcja warunkowa / wyrażenie warunkowe /
Laurence Gonsalves
1
Zgaduję, że nie miałeś na myśli obu maini main2być dokładnie tym samym?
ColinD,
imponujący. Aż do teraz nie wiedziałem, że można skompilować kod bajtowy.
Kyle,
2
@Kyle: Skompilowałem Javę, a następnie zdekompilowałem za pomocą javap.
Jon Skeet,
1
@Kyle: Dokładnie. Spodziewałem się, że kod bajtowy będzie identyczny . W tej chwili jest prawie identyczny :)
Jon Skeet
10

Oba twoje przykłady prawdopodobnie skompilują się do identycznego lub prawie identycznego kodu bajtowego, więc nie powinno być różnicy w wydajności.

Gdyby była różnica w szybkości wykonywania, nadal powinieneś używać najbardziej idiomatycznej wersji (która byłaby drugą do przypisywania pojedynczej zmiennej na podstawie prostego warunku i dwóch prostych wyrażeń podrzędnych, a pierwszą do wykonywania bardziej złożonych operacji lub operacje, które nie mieszczą się w jednej linii).

Victor Nicollet
źródło
8

Te są takie same. Oba są dość szybkie, zwykle około 10-30 nanosekund. (w zależności od sposobu użytkowania) Czy ten przedział czasowy jest dla Ciebie ważny?

Powinieneś robić to, co uważasz za najbardziej oczywiste.

Peter Lawrey
źródło
4

Wystarczy dodać do wszystkich pozostałych odpowiedzi:

Drugie wyrażenie jest często nazywane trzeciorzędnym / potrójnym operatorem / instrukcją. Może być bardzo przydatne, ponieważ zwraca wyrażenie. Czasami sprawia, że ​​kod jest bardziej przejrzysty w przypadku typowych krótkich instrukcji.

Secko
źródło
4
Świetny przykład tego w praktyce: w Javie, jeśli muszę dokonać ostatecznego ciągu znaków w oparciu o wynik wyrażenia, mogę użyć trójskładniowej składni final String whichTable = (Integer.parseInt (clientId)> 500)? "serverClients": "offlineClients"; Następnie mogę użyć wartości wyrażenia w miejscach, w których tabela musi być ostateczna. Poniższe byłoby niedozwolone: ​​final String whichTable = ""; if (Integer.parseInt (clientId)> 500) {whichTable = "serverClients"; } else {whichTable = "offlineClients"; }
James Perih,
@JamesPerih W przypadku finalpola, możesz użyć bloków konstruktora, aby ustawić wartość (chociaż operator warunkowy wygląda miliard razy lepiej w IMO), a ze zmiennymi lokalnymi możesz przypisać wartość przed pierwszym użyciem później w bloku kodu, Jestem w środku . Myślę, że jedyny przypadek, w którym trójskładnik dałby przewagę, if-elseto wywołanie super(...)lub this(...)wewnątrz konstruktora.
Kröw
3

ani - będą kompilowane do tego samego.

Freddie
źródło
0

Operator trójargumentowy jest szybszy niż warunek if-else.

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

Wyniki testu:

Trail-1:

Czas na if-else: 63

Czas na trójkę: 31

Trail-2:

Czas na coś innego: 78

Czas na trójkę: 47

Trail-3:

Czas na if-else: 94

Czas na trójkę: 31

Trail-4:

Czas na coś innego: 78

Czas na trójkę: 47

rmkyjv
źródło
Podczas uruchamiania przykładu uzyskałem dokładnie odwrotne wyniki, co pokazuje, że wyniki są niewiarygodne. Niestety wpadasz w pułapkę mikroznaków - notorycznie trudno jest poprawnie wykonać mikroznakowanie. Kilka przykładów można znaleźć tutaj: stackoverflow.com/questions/2842695/what-is-microbenchmarking
Rogach
Twój konkretny przykład ma co najmniej następujące problemy: 4 próby to nigdzie nie wystarczające, uruchamiasz testy zawsze w tej samej kolejności (pierwszy if-else, drugi trójskładnik), nie rozgrzewasz maszyny JVM przed uruchomieniem testów itp.
Rogach