2013-06-17 9 views
5

Mam małe pytanie dotyczące wydajności, przy pracy z klauzulą ​​catch catch, lepiej jest podać dokładny wyjątek, który można uzyskać lub po prostu użyć wyjątku, czy jest lepszy? Przykład:Podaj wyjątek, czy nie?

try { 
    whatever 
} catch (NullPointerException ex) { 
    whatever 
} 

lub jeśli nie masz nic przeciwko temu, jaki rodzaj wyjątek:

try { 
    whatever 
} catch (Exception ex) { 
    whatever 
} 

bo wiem, że można stosować różne wyjątki wywołać różne efekty, ale jestem po prostu z prośbą o wydajność.

+0

Domyślam się, że wydajność jest taka sama, z wyjątkiem przypadku złapania 'Throwable', ponieważ wszystko bardziej szczegółowe niż to będzie wymagało od programu sprawdzenia typu' Throwable'. To tylko zgadnij. – asteri

+0

Dlaczego nie spróbować samemu przeprowadzić testu porównawczego i opublikować wyników tutaj. – anubhava

+5

@anubhava Ponieważ za każdym razem, gdy próbuję coś przetestować, powiedziano mi, że nie testuję poprawnie. Haha :) – asteri

Odpowiedz

0

Jeśli wydajność jest w jakikolwiek sposób istotna z punktu widzenia wychwytywania wyjątków, nie stanowi wyjątku.

Wszystko, co robimy, to test, więc wątpię, żeby to miało jakikolwiek wpływ.

Catching Exception powinno być bardzo rzadkie.

Jeśli robisz

try 
{ 
    whatever 
} 
catch (MyException10 ex10) 
{ 
    whatever 
} 
catch (MyException9 ex9) 
{ 
    whatever 
} 
... 
catch (MyException1 ex1) 
{ 
    whatever 
} 

Następnie można argumentować, potencjalny wzrost wydajności, ale to byłoby przerobienie kodu tak jeden bit nie rzucać fura wyjątki ...

+1

Uważam, że OP jest bardziej zainteresowany procesem wychwytywania wyjątków w ogóle i jeśli Java będzie traktować wychwytywanie 'wyjątku' inaczej niż przechwytywanie' MoreSpecificException'. – arshajii

+0

Czasami ktoś może utknąć przy pomocy API, gdzie nie można uniknąć rzucania mnóstwa wyjątków. Przykładowo, można mieć słownikowy interfejs, który zawiera metodę "pobierz element", która wyrzuca, jeśli element nie istnieje, ale nie zawiera żadnych innych sposobów sprawdzania, czy dany element istnieje. Fakt, że taki interfejs może być źle zaprojektowany, niekoniecznie umożliwia jego zmianę. W takich przypadkach wydajność obsługi wyjątków może być bardzo ważna. – supercat

+0

To byłby jeden z drzewa dziedziczenia i musielibyśmy bardzo często wyrzucać wyjątki.W takim przypadku nie są wyjątkowe. Jeśli zostaniesz wypchany API, o którym wspomniał superkat, być może, ale będę szukał sposobów, które nie będą przeszkadzały w bardziej wydajnym obchodzeniu się z nimi. Koszt tego testu jest trywialny w porównaniu do samego samego wyjątku. –

0

Najlepszą wydajność porady dotyczące bloku prób/catch, które mogę wymyślić, to: sprawić, by były małe.

Jeśli masz kawałek kodu, który może rzucić, powiedzmy, trzy wyjątki, zrobić:

try { 
    piece1(); 
} catch (Exception1 e) { 
    // whatever 
} 

try { 
    piece2(); 
} catch (Exception2 e) { 
    // whatever 
} 

try { 
    piece3(); 
} catch (Exception3 e) { 
    // whatever 
} 

ten sposób można return/break/continue wcześnie. I traktuj każdy wyjątek w taki sposób, w jaki ma być traktowany. Rzadko zdarza się, że "nie przeszkadza", co wyjątek został złapany.

Należy również zauważyć, że nadmiernie szeroki blok catch może "ukrywać" wyjątki, których nie można się spodziewać. W szczególności, wszystkie RuntimeException s są przechwytywane, gdy złapiesz Exception (i obejmuje NullPointerException, ArrayIndexOutOfBoundsException i inne niceties). A to nie jest dobre (tm).

2

Sugerowałbym, że poprawną odpowiedzią tutaj jest użycie odpowiedniej obsługi wyjątków z powodów programowych w przeciwieństwie do wydajności. Jeśli (ignorując wydajność) byłoby bardziej odpowiednie złapać NullPointerException, a następnie to zrobić.

Wyjątki to wyjątek. Powinny one zdarzać się rzadko, aby wydajność w przetwarzaniu wyjątków była mniej ważna niż poprawność.

Jeśli aplikacja regularnie oczekuje tej sytuacji, powinna obsłużyć ją za pośrednictwem mechanizmu innego niż wyjątek. Jest to szczególnie ważne, jeśli martwisz się wydajnością, ponieważ wyjątki od rzucania są ZAWSZE kosztowne.

4

Zgodnie z moimi testami istnieje brak znaczącej różnicy w wydajności.

Każda próba próbuje dziesięć milionów każdego scenariusza, a następnie porównuje czasy działania w nanosekundach i zaokrąglonych sekundach. Jest to faktycznie sprzeczne z moją pierwotną hipotezą, ponieważ sądziłem, że złapanie numeru Throwable spowoduje wyraźną poprawę.

Zacząłem również zdawać sobie sprawę, że część tego może wynikać z wpływu optymalizatora, dlatego stworzyłem bardziej skomplikowany przykład, który zawiera poniżej liczby pseudolosowe, myśląc, że to złagodzi potencjalny wpływ optymalizatora na kodzie.

(nie będę pouczać o właściwym wykorzystaniu catch bloków, a pytanie brzmi konkretnie o wydajność, nie najlepszych praktyk.)

wiele danych poniżej tego punktu!

przejazd 1 Wyniki:

Exception: 7196141955 (7.196s) 
NumberFormatException: 7736401837 (7.736s) 
Throwable: 6818656505 (6.819s) 

trwającej 2 Wyniki:

Exception: 7262897545 (7.263s) 
NumberFormatException: 7056116050 (7.056s) 
Throwable: 7108232206 (7.108s) 

prowadzony 3 Wyniki:

Exception: 7088967045 (7.089s) 
NumberFormatException: 7020495455 (7.020s) 
Throwable: 7192925684 (7.193s) 

Run 4 Wyniki:

Exception: 6916917328 (6.917s) 
NumberFormatException: 7690084994 (7.690s) 
Throwable: 6906011513 (6.906s) 

Run 5 Wyniki:

Exception: 7247571874 (7.248s) 
NumberFormatException: 6818511040 (6.819s) 
Throwable: 6813286603 (6.813s) 

Kod

import java.math.BigDecimal; 
import java.math.RoundingMode; 

public class Test { 

    private static final int TRIALS = 10000000; 
    private static final int NANOS_IN_SECOND = 1000000000; 
    private static final int DECIMAL_PRECISION = 3; 
    private static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP; 

    public static void main(String[] args) { 

     long firstStart = System.nanoTime(); 

     for(int i = 0; i < TRIALS; i++) { 
      try { 
       throw new NumberFormatException(); 
      } 
      catch(Exception e) { 

      } 
     } 

     long firstEnd = System.nanoTime(); 

     long secondStart = System.nanoTime(); 

     for(int i = 0; i < TRIALS; i++) { 
      try { 
       throw new NumberFormatException(); 
      } 
      catch(NumberFormatException e) { 

      } 
     } 

     long secondEnd = System.nanoTime(); 

     long thirdStart = System.nanoTime(); 

     for(int i = 0; i < TRIALS; i++) { 
      try { 
       throw new NumberFormatException(); 
      } 
      catch(Throwable e) { 

      } 
     } 

     long thirdEnd = System.nanoTime(); 

     long exception = firstEnd - firstStart; 
     long numberFormatException = secondEnd - secondStart; 
     long throwable = thirdEnd - thirdStart; 

     BigDecimal exceptionSeconds = new BigDecimal((double)exception/(double)NANOS_IN_SECOND); 
     BigDecimal numberFormatExceptionSeconds = new BigDecimal((double)numberFormatException/(double)NANOS_IN_SECOND); 
     BigDecimal throwableSeconds = new BigDecimal((double)throwable/(double)NANOS_IN_SECOND); 

     exceptionSeconds = exceptionSeconds.setScale(DECIMAL_PRECISION, ROUNDING_MODE); 
     numberFormatExceptionSeconds = numberFormatExceptionSeconds.setScale(DECIMAL_PRECISION, ROUNDING_MODE); 
     throwableSeconds = throwableSeconds.setScale(DECIMAL_PRECISION, ROUNDING_MODE); 

     System.out.println("Exception: " + exception + " (" + exceptionSeconds + "s)"); 
     System.out.println("NumberFormatException: " + numberFormatException + " (" + numberFormatExceptionSeconds + "s)"); 
     System.out.println("Throwable: " + throwable + " (" + throwableSeconds + "s)"); 

    } 

} 

Więcej zawiłe, pseudolosowych kod

Stworzyłem to, aby upewnić się, że optymalizator nie po prostu "ignoruje" cały proces rzutowania/wychwytywania, zdając sobie sprawę, że blok kodu będzie zawsze przepływał do catch. Podejmując próbę wykonania Integer.parseInt() losowo wybranego String (ale zawsze niepoprawnego), oznacza to, że kompilator nie może wiedzieć do czasu wykonania, czy dany przebieg przez pętle for() jest ważny, czy też nie.

Zgodnie z oczekiwaniami z pierwszego eksperymentu nie ma znaczącej różnicy między tymi trzema scenariuszami.

przejazd 1 Wyniki:

Exception: 10988431371 (10.988s) 
NumberFormatException: 11360698958 (11.361s) 
Throwable: 10539041505 (10.539s) 

trwającej 2 Wyniki:

Exception: 12468860076 (12.469s) 
NumberFormatException: 11852429194 (11.852s) 
Throwable: 11859547560 (11.860s) 

prowadzony 3 Wyniki:

Exception: 10618082779 (10.618s) 
NumberFormatException: 10718252324 (10.718s) 
Throwable: 10327709072 (10.328s) 

Run 4 Wyniki:

Exception: 11031135405 (11.031s) 
NumberFormatException: 10689877480 (10.690s) 
Throwable: 10668345685 (10.668s) 

Run 5 Wyniki:

Exception: 11513727192 (11.514s) 
NumberFormatException: 11581826079 (11.582s) 
Throwable: 12488301109 (12.488s) 

Kod

import java.math.BigDecimal; 
import java.math.RoundingMode; 
import java.util.Random; 

public class Test { 

    private static final int TRIALS = 10000000; 
    private static final int NANOS_IN_SECOND = 1000000000; 
    private static final int DECIMAL_PRECISION = 3; 
    private static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP; 

    private static final String[] TEST_STRINGS = { 
     "lawl", 
     "rofl", 
     "trololo", 
     "foo", 
     "bar" 
    }; 

    private static final Random RANDOM = new Random(); 



    public static void main(String[] args) { 

     long firstStart = System.nanoTime(); 

     for(int i = 0; i < TRIALS; i++) { 
      try { 
       Integer.parseInt(TEST_STRINGS[RANDOM.nextInt(TEST_STRINGS.length)]); 
      } 
      catch(Exception e) { 

      } 
     } 

     long firstEnd = System.nanoTime(); 

     long secondStart = System.nanoTime(); 

     for(int i = 0; i < TRIALS; i++) { 
      try { 
       Integer.parseInt(TEST_STRINGS[RANDOM.nextInt(TEST_STRINGS.length)]); 
      } 
      catch(NumberFormatException e) { 

      } 
     } 

     long secondEnd = System.nanoTime(); 

     long thirdStart = System.nanoTime(); 

     for(int i = 0; i < TRIALS; i++) { 
      try { 
       Integer.parseInt(TEST_STRINGS[RANDOM.nextInt(TEST_STRINGS.length)]); 
      } 
      catch(Throwable e) { 

      } 
     } 

     long thirdEnd = System.nanoTime(); 

     long exception = firstEnd - firstStart; 
     long numberFormatException = secondEnd - secondStart; 
     long throwable = thirdEnd - thirdStart; 

     BigDecimal exceptionSeconds = new BigDecimal((double)exception/(double)NANOS_IN_SECOND); 
     BigDecimal numberFormatExceptionSeconds = new BigDecimal((double)numberFormatException/(double)NANOS_IN_SECOND); 
     BigDecimal throwableSeconds = new BigDecimal((double)throwable/(double)NANOS_IN_SECOND); 

     exceptionSeconds = exceptionSeconds.setScale(DECIMAL_PRECISION, ROUNDING_MODE); 
     numberFormatExceptionSeconds = numberFormatExceptionSeconds.setScale(DECIMAL_PRECISION, ROUNDING_MODE); 
     throwableSeconds = throwableSeconds.setScale(DECIMAL_PRECISION, ROUNDING_MODE); 

     System.out.println("Exception: " + exception + " (" + exceptionSeconds + "s)"); 
     System.out.println("NumberFormatException: " + numberFormatException + " (" + numberFormatExceptionSeconds + "s)"); 
     System.out.println("Throwable: " + throwable + " (" + throwableSeconds + "s)"); 

    } 

} 
+0

+1 za wysiłki, aby napisać ten test porównawczy i zaufaj mi, że wydaje się właściwym testowaniem porównawczym: P – anubhava

+0

Dziękuję za wszystkie te testy, które bardzo mi pomogły :) –

1

W tym przypadku też nie. try/catch jest stosunkowo kosztowny i powinien być stosowany oszczędnie. Lepszym rozwiązaniem jest ręczne sprawdzanie wartości null niż wychwytywanie wyjątku NullPointerException.