2013-10-04 11 views
7

W pętli Java, czy bardziej efektywne jest użycie flagi boolean zamiast instrukcji if?Czy bardziej efektywne jest użycie flagi lub klauzuli "if"?

Spójrz na te dwa fragmenty kodu.

Korzystanie flagę:

public boolean isSomethingForAnyone() { 
    boolean flag = false; 
    for (Item item : listOfItems) { 
     flag = flag || item.isSomething(); 
    } 
    return flag; 
} 

Korzystanie oświadczenie if:

public boolean isSomethingForAnyone() { 
    for (Item item : listOfItems) { 
     if (item.isSomething()) 
      return true; 
    } 
    return false; 
} 

metody ze stwierdzeniem if jest oczywiście szybciej, jeśli isSomething() powrót true na pierwszej iteracji. Ale czy jest średnio szybszy, czy rozgałęzianie powoduje spowolnienie na tyle, że jest wolniejsze? Czy sytuacja jest inna, jeśli pętla jest szybsza? Tutaj użyłem pętli for-each dla uproszczenia, które moim zdaniem jest wolniejsze niż iterowanie przez tablicę z licznikiem.

+0

dla każdego nie jest wolniejsza niż zwykła pętla for. – JNL

+1

Najpierw zawsze będzie iterować po wszystkich elementach; sekunda zatrzyma się, gdy jeden element jest prawdziwy. –

Odpowiedz

4

Dwie części kodu nie są całkiem równoważne.

Mimo że wywołuje się tylko item.isSomething() tyle razy, ile potrzeba (w przeciwieństwie do mojej oryginalnej odpowiedzi), pierwsza wersja wciąż próbuje powtórzyć całą resztę kolekcji.

Wyobraź sobie implementację Item.isSomething(), która zmodyfikowała kolekcję, w której element został znaleziony (jeśli zwróci true). W tym momencie pierwszy fragment kodu rzuciłby ConcurrentModificationException zakładając, że jest to "zwykła" kolekcja - podczas gdy drugi fragment kodu po prostu zwróci true.

Zasadniczo drugi fragment kodu jest bardziej efektywny: sprawdza tylko tyle pozycji, ile jest wymagane do określenia odpowiedzi, zamiast przez wszystko. Może się zdarzyć, że wydajność jest różna, nie ma znaczenia - zwłaszcza jeśli kolekcja jest mała - ale zależy to od kontekstu.

Który znajduje się więcej czytelny to inna sprawa - jest prawdopodobne, że wydajność nie będzie znacząca, choć zależy to od kontekstu. Osobiście uważam, że druga wersja jest bardziej czytelna , a także bardziej wydajna, więc zawsze będę z niej korzystał. (Cóż, dodaję nawiasy klamrowe do całego ciała oświadczenia if, ale to wszystko.)

+0

Myślę, że standardowy styl kodowania zakłada, że ​​metoda o nazwie 'isX' będzie czysta, tj. Nie będzie miała żadnych skutków ubocznych. – selig

+0

@selig Tak, właśnie o to mi chodziło. – Pietu1998

+0

@selig: Tak, to rozsądne założenie dla * nas * do zrobienia - ale nie byłoby rozsądne, aby optymalizator dokonał tego założenia i wcześniej zakończył pętlę. (Możliwe, że naprawdę inteligentny JIT mógłby to sprawdzić, ale wydaje się to mało prawdopodobne.Nie myślę, że jest to bardzo często sytuacja do optymalizacji.) –

0

Czy robisz tę pętlę dosłownie miliard razy? jeśli nie, różnica jest prawdopodobnie niemożliwa do zmierzenia.

możesz spojrzeć na wygenerowany kod bajtowy i zobaczyć dokładnie, co się dzieje, ale kompilator i jit oraz sam vm prawdopodobnie zoptymalizują wszelkie różnice.

0

Jeśli chcesz wiedzieć, który jest "szybszy" na twoim komputerze, uruchom go.

Jeśli chcemy wiedzieć, które z nich wymaga więcej instrukcji, możemy spojrzeć na kod bajtowy.

Na pierwszej metodzie możemy uzyskać to (nazwijmy javap -c)

Code: 
    0: iconst_0  
    1: istore_1  
    2: getstatic  #2     // Field listOfItems:Ljava/util/List; 
    5: invokeinterface #3, 1   // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 
    10: astore_2  
    11: aload_2  
    12: invokeinterface #4, 1   // InterfaceMethod java/util/Iterator.hasNext:()Z 
    17: ifeq   50 
    20: aload_2 
    21: invokeinterface #5, 1   // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 
    26: checkcast  #6     // class Item 
    29: astore_3  
    30: iload_1  
    31: ifne   41 
    34: aload_3  
    35: invokevirtual #7     // Method Item.isSomething:()Z 
    38: ifeq   45 
    41: iconst_1  
    42: goto   46 
    45: iconst_0  
    46: istore_1  
    47: goto   11 
    50: iload_1  
    51: ireturn 

Jesteśmy zainteresowani we wnętrzu pętli tj linie 29-46 (linie 11-26 są Iterator rzeczy).Tak około 10 instrukcji.

Na drugiej metodzie możemy uzyskać to:

Code: 
     0: getstatic  #2     // Field listOfItems:Ljava/util/List; 
     3: invokeinterface #3, 1   // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 
     8: astore_1  
     9: aload_1  
     10: invokeinterface #4, 1   // InterfaceMethod java/util/Iterator.hasNext:()Z 
     15: ifeq   40 
     18: aload_1  
     19: invokeinterface #5, 1   // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 
     24: checkcast  #6     // class Item 
     27: astore_2  
     28: aload_2  
     29: invokevirtual #7     // Method Item.isSomething:()Z 
     32: ifeq   37 
     35: iconst_1  
     36: ireturn  
     37: goto   9 
     40: iconst_0  
     41: ireturn  

Linie miejsca to 27-37. Tak więc 7 instrukcji.

Z liczbowego punktu widzenia druga metoda wychodzi na wierzch (pamiętaj, że zakładamy, że wszystkie operacje na stosie wykonują ten sam czas).

+0

Patrząc na kod bajtowy nie odzwierciedla to, co faktycznie się dzieje, ani jakie instrukcje są naprawdę wykonane. Możesz * bardzo, bardzo * źle, zakładając, że kod bajtowy jest wykonywany tak jak jest. To niesamowite, co kompilator JIT może zoptymalizować w niektórych sytuacjach - ale byłbym zaskoczony, gdyby JIT zoptymalizował * ten * kod, aby obie pętle były równoważne. –

Powiązane problemy