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;

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 są 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.


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).


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.


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.


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


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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.