Która część zgłoszenia wyjątku jest droga?

256

W Javie używanie rzucania / łapania jako części logiki, gdy tak naprawdę nie ma błędu, jest ogólnie złym pomysłem (częściowo), ponieważ rzucanie i wychwytywanie wyjątku jest kosztowne, a wykonywanie go wielokrotnie w pętli jest zwykle znacznie wolniejsze niż inne struktury kontrolne, które nie wymagają zgłaszania wyjątków.

Moje pytanie brzmi: czy koszty ponoszone są w samym rzucie / wyłapaniu, czy podczas tworzenia obiektu wyjątku (ponieważ otrzymuje on wiele informacji o środowisku wykonawczym, w tym stos wykonywania)?

Innymi słowy, jeśli to zrobię

Exception e = new Exception();

ale nie rzucaj, czy to większość kosztu rzucania, czy też obsługa rzutów i łap jest kosztowna?

Nie pytam, czy umieszczenie kodu w bloku try / catch zwiększa koszt wykonania tego kodu, pytam, czy złapanie wyjątku jest kosztowną częścią, czy utworzenie (wywołanie konstruktora) wyjątku jest kosztowną częścią .

Innym sposobem zadawania tego pytania jest to, że jeśli stworzyłem jeden przypadek wyjątku i rzucałem go w kółko, czy byłoby to znacznie szybsze niż tworzenie nowego wyjątku za każdym razem, gdy rzucam?

Martin Carney
źródło
20
Uważam, że wypełnia i wypełnia ślad stosu.
Elliott Frisch
„jeśli stworzyłem jeden przypadek wyjątku i ciągle go rzucałem i łapałem”, po utworzeniu wyjątku jego ślad w stosie jest zapełniany, co oznacza, że ​​zawsze będzie taki sam, niezależnie od miejsca, z którego został wyrzucony. Jeśli stacktrace nie jest dla ciebie ważny, możesz wypróbować swój pomysł, ale w niektórych przypadkach może to utrudnić debugowanie.
Pshemo
2
@Pshemo Nie planuję faktycznie zrobić to w kodzie, pytam o wydajności, a przy użyciu tego absurdu jako przykład gdzie może sprawić różnicę.
Martin Carney
@MartinCarney Dodałem odpowiedź do ostatniego akapitu, tzn. Zapisanie wyjątku w pamięci podręcznej przyniosłoby wzrost wydajności. Jeśli to przydatne, mogę dodać kod, jeśli nie, mogę usunąć odpowiedź.
Harry

Odpowiedzi:

267

Tworzenie obiektu wyjątku nie jest droższe niż tworzenie innych zwykłych obiektów. Główny koszt jest ukryty w natywnej fillInStackTracemetodzie, która przechodzi przez stos wywołań i zbiera wszystkie wymagane informacje do zbudowania śledzenia stosu: klasy, nazwy metod, numery linii itp.

Mit o wysokich kosztach wyjątków wynika z faktu, że większość Throwablekonstruktorów domyślnie dzwoni fillInStackTrace. Istnieje jednak jeden konstruktor, który utworzy Throwableślad bez stosu. Umożliwia tworzenie rzutów, które są bardzo szybkie w tworzeniu. Innym sposobem na utworzenie lekkich wyjątków jest zastąpienie fillInStackTrace.


A co z rzuceniem wyjątku?
W rzeczywistości, to zależy od tego, gdzie rzucony wyjątek jest złapany .

Jeśli zostanie przechwycony tą samą metodą (lub ściślej mówiąc, w tym samym kontekście, ponieważ kontekst może obejmować kilka metod ze względu na wstawianie), to throwjest tak szybki i prosty jak goto(oczywiście po kompilacji JIT).

Jeśli jednak catchblok znajduje się gdzieś głębiej na stosie, JVM musi rozwinąć ramki stosu, co może potrwać znacznie dłużej. Zajmuje to jeszcze więcej czasu, jeśli w grę wchodzą synchronizedbloki lub metody, ponieważ odwijanie oznacza zwolnienie monitorów należących do usuniętych ramek stosu.


Mógłbym potwierdzić powyższe stwierdzenia odpowiednimi testami porównawczymi, ale na szczęście nie muszę tego robić, ponieważ wszystkie aspekty zostały już doskonale omówione w stanowisku inżyniera wydajności HotSpot Alexeya Shipileva: Wyjątkowa wydajność wyjątku Lil .

apangin
źródło
8
Jak zauważono w artykule i poruszono tutaj, wynik jest taki, że koszt rzucania / łapania wyjątków jest wysoce zależny od głębokości połączeń. Chodzi o to, że stwierdzenie „wyjątki są drogie” nie jest tak naprawdę poprawne. Bardziej poprawnym stwierdzeniem jest to, że wyjątki „mogą” być kosztowne. Szczerze mówiąc, myślę, że powiedzenie tylko o wyjątkach dotyczących „naprawdę wyjątkowych przypadków” (jak w artykule) jest zbyt mocno sformułowane. Są idealne do prawie wszystkiego poza normalnym przepływem powrotnym i trudno jest wykryć wpływ na wydajność używania ich w ten sposób w prawdziwej aplikacji.
JimmyJames,
14
Może warto oprocentować wyjątki. Nawet w najgorszym przypadku opisanym w tym dość wyczerpującym artykule (rzucanie i wyławianie dynamicznego wyjątku za pomocą faktycznie śledzonego stosu, 1000 klatek na stosie), zajmuje 80 mikro sekund. Może to być znaczące, jeśli Twój system musi przetwarzać tysiące wyjątków na sekundę, ale poza tym nie warto się martwić. I to jest najgorszy przypadek; jeśli twoje stacktrace są trochę rozsądniejsze lub nie przeszukujesz ich stacktrace, możemy przetworzyć prawie milion wyjątków na sekundę.
meriton
13
Podkreślam to, ponieważ wiele osób, czytając, że wyjątki są „drogie”, nigdy nie przestaje pytać „drogie w porównaniu z tym, co”, ale zakłada, że ​​są one „kosztowną częścią swojego programu”, którymi bardzo rzadko są.
meriton
2
Jest jedna część, która nie została tutaj wymieniona: potencjalny koszt uniemożliwienia zastosowania optymalizacji. Ekstremalnym przykładem może być JVM, który nie chce unikać „mętnych” śladów stosu, ale widziałem (mikro) testy porównawcze, w których obecność lub brak wyjątków czyniłaby lub przełamałaby optymalizacje w C ++ wcześniej.
Matthieu M.
3
@MatthieuM. Wyjątki i bloki try / catch nie uniemożliwiają wstawiania JVM. W przypadku metod skompilowanych rzeczywiste ślady stosu są odtwarzane z wirtualnej tabeli ramek stosu przechowywanej jako metadane. Nie mogę sobie przypomnieć optymalizacji JIT, która jest niezgodna z try / catch. Sama struktura try / catch nie dodaje niczego do kodu metody, istnieje tylko jako tabela wyjątków oprócz kodu.
apangin 02.04.16
72

Pierwsza operacja w większości Throwable konstruktorów jest wypełnienie śladu stosu, czyli tam, gdzie jest większość kosztów.

Istnieje jednak chroniony konstruktor z flagą, aby wyłączyć śledzenie stosu. Ten konstruktor jest również dostępny podczas rozszerzania Exception. Jeśli utworzysz niestandardowy typ wyjątku, możesz uniknąć tworzenia śladu stosu i uzyskać lepszą wydajność kosztem mniejszej ilości informacji.

Jeśli utworzysz pojedynczy wyjątek dowolnego typu w zwykły sposób, możesz go ponownie rzucić wiele razy, bez narzutu związanego z wypełnianiem śladu stosu. Jednak ślad stosu będzie odzwierciedlał to, gdzie został zbudowany, a nie gdzie został rzucony w konkretnym przypadku.

Obecne wersje Java próbują zoptymalizować tworzenie śledzenia stosu. Wywoływany jest kod macierzysty w celu wypełnienia śladu stosu, który zapisuje ślad w lżejszej natywnej strukturze. Odpowiednie StackTraceElementobiekty Java są tworzone leniwie z tego rekordu tylko wtedy getStackTrace(), gdy printStackTrace()wywoływane są metody, lub inne metody wymagające śledzenia.

Jeśli wyeliminujesz generowanie śladu stosu, drugim głównym kosztem jest odwrócenie stosu między rzutem a złapaniem. Im mniej ramek pośrednich napotkanych przed wychwyceniem wyjątku, tym szybciej będzie to możliwe.

Zaprojektuj swój program tak, aby wyjątki były zgłaszane tylko w naprawdę wyjątkowych przypadkach, a optymalizacje takie jak te są trudne do uzasadnienia.

erickson
źródło
25

Tutaj jest dobry opis wyjątków.

http://shipilev.net/blog/2014/exceptional-performance/

Konkluzja jest taka, że ​​konstrukcja śladu stosu i rozwijanie stosu są drogimi częściami. Poniższy kod wykorzystuje funkcję, w 1.7której możemy włączać i wyłączać ślady stosu. Następnie możemy to wykorzystać, aby zobaczyć, jakie koszty mają różne scenariusze

Poniżej przedstawiono terminy tworzenia samego obiektu. Dodałem Stringtutaj, abyś mógł zobaczyć, że bez zapisania stosu prawie nie ma różnicy w tworzeniu JavaExceptionobiektu i String. Przy włączonym zapisywaniu stosu różnica jest dramatyczna, tzn. Co najmniej o jeden rząd wielkości wolniejsza.

Time to create million String objects: 41.41 (ms)
Time to create million JavaException objects with    stack: 608.89 (ms)
Time to create million JavaException objects without stack: 43.50 (ms)

Poniżej pokazano, ile czasu zajęło mu powrót z rzutu na określonej głębokości milion razy.

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|           1428|             243| 588 (%)|
|   15|           1763|             393| 449 (%)|
|   14|           1746|             390| 448 (%)|
|   13|           1703|             384| 443 (%)|
|   12|           1697|             391| 434 (%)|
|   11|           1707|             410| 416 (%)|
|   10|           1226|             197| 622 (%)|
|    9|           1242|             206| 603 (%)|
|    8|           1251|             207| 604 (%)|
|    7|           1213|             208| 583 (%)|
|    6|           1164|             206| 565 (%)|
|    5|           1134|             205| 553 (%)|
|    4|           1106|             203| 545 (%)|
|    3|           1043|             192| 543 (%)| 

Poniżej prawie na pewno rażące jest uproszczenie ...

Jeśli weźmiemy pod uwagę głębokość 16 przy zapisywaniu stosu, wówczas tworzenie obiektów zajmuje około ~ 40% czasu, rzeczywisty ślad stosu stanowi zdecydowaną większość tego. ~ 93% tworzenia wystąpienia obiektu JavaException wynika z pobrania śladu stosu. Oznacza to, że odwijanie stosu w tym przypadku zajmuje pozostałe 50% czasu.

Kiedy wyłączamy konta tworzenia obiektów śledzenia stosu dla znacznie mniejszej części, tj. 20%, a rozwijanie stosu stanowi teraz 80% czasu.

W obu przypadkach odwijanie stosu zajmuje dużą część całkowitego czasu.

public class JavaException extends Exception {
  JavaException(String reason, int mode) {
    super(reason, null, false, false);
  }
  JavaException(String reason) {
    super(reason);
  }

  public static void main(String[] args) {
    int iterations = 1000000;
    long create_time_with    = 0;
    long create_time_without = 0;
    long create_string = 0;
    for (int i = 0; i < iterations; i++) {
      long start = System.nanoTime();
      JavaException jex = new JavaException("testing");
      long stop  =  System.nanoTime();
      create_time_with += stop - start;

      start = System.nanoTime();
      JavaException jex2 = new JavaException("testing", 1);
      stop = System.nanoTime();
      create_time_without += stop - start;

      start = System.nanoTime();
      String str = new String("testing");
      stop = System.nanoTime();
      create_string += stop - start;

    }
    double interval_with    = ((double)create_time_with)/1000000;
    double interval_without = ((double)create_time_without)/1000000;
    double interval_string  = ((double)create_string)/1000000;

    System.out.printf("Time to create %d String objects: %.2f (ms)\n", iterations, interval_string);
    System.out.printf("Time to create %d JavaException objects with    stack: %.2f (ms)\n", iterations, interval_with);
    System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)\n", iterations, interval_without);

    JavaException jex = new JavaException("testing");
    int depth = 14;
    int i = depth;
    double[] with_stack    = new double[20];
    double[] without_stack = new double[20];

    for(; i > 0 ; --i) {
      without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000;
      with_stack[i]    = jex.timerLoop(i, iterations, 1)/1000000;
    }
    i = depth;
    System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)|\n");
    for(; i > 0 ; --i) {
      double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
      System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| \n", i + 2, with_stack[i] , without_stack[i], ratio);
      //System.out.printf("%d\t%.2f (ms)\n", i, ratio);
    }
  }
 private int thrower(int i, int mode) throws JavaException {
    ExArg.time_start[i] = System.nanoTime();
    if(mode == 0) { throw new JavaException("without stack", 1); }
    throw new JavaException("with stack");
  }
  private int catcher1(int i, int mode) throws JavaException{
    return this.stack_of_calls(i, mode);
  }
  private long timerLoop(int depth, int iterations, int mode) {
    for (int i = 0; i < iterations; i++) {
      try {
        this.catcher1(depth, mode);
      } catch (JavaException e) {
        ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]);
      }
    }
    //long stop = System.nanoTime();
    return ExArg.time_accum[depth];
  }

  private int bad_method14(int i, int mode) throws JavaException  {
    if(i > 0) { this.thrower(i, mode); }
    return i;
  }
  private int bad_method13(int i, int mode) throws JavaException  {
    if(i == 13) { this.thrower(i, mode); }
    return bad_method14(i,mode);
  }
  private int bad_method12(int i, int mode) throws JavaException{
    if(i == 12) { this.thrower(i, mode); }
    return bad_method13(i,mode);
  }
  private int bad_method11(int i, int mode) throws JavaException{
    if(i == 11) { this.thrower(i, mode); }
    return bad_method12(i,mode);
  }
  private int bad_method10(int i, int mode) throws JavaException{
    if(i == 10) { this.thrower(i, mode); }
    return bad_method11(i,mode);
  }
  private int bad_method9(int i, int mode) throws JavaException{
    if(i == 9) { this.thrower(i, mode); }
    return bad_method10(i,mode);
  }
  private int bad_method8(int i, int mode) throws JavaException{
    if(i == 8) { this.thrower(i, mode); }
    return bad_method9(i,mode);
  }
  private int bad_method7(int i, int mode) throws JavaException{
    if(i == 7) { this.thrower(i, mode); }
    return bad_method8(i,mode);
  }
  private int bad_method6(int i, int mode) throws JavaException{
    if(i == 6) { this.thrower(i, mode); }
    return bad_method7(i,mode);
  }
  private int bad_method5(int i, int mode) throws JavaException{
    if(i == 5) { this.thrower(i, mode); }
    return bad_method6(i,mode);
  }
  private int bad_method4(int i, int mode) throws JavaException{
    if(i == 4) { this.thrower(i, mode); }
    return bad_method5(i,mode);
  }
  protected int bad_method3(int i, int mode) throws JavaException{
    if(i == 3) { this.thrower(i, mode); }
    return bad_method4(i,mode);
  }
  private int bad_method2(int i, int mode) throws JavaException{
    if(i == 2) { this.thrower(i, mode); }
    return bad_method3(i,mode);
  }
  private int bad_method1(int i, int mode) throws JavaException{
    if(i == 1) { this.thrower(i, mode); }
    return bad_method2(i,mode);
  }
  private int stack_of_calls(int i, int mode) throws JavaException{
    if(i == 0) { this.thrower(i, mode); }
    return bad_method1(i,mode);
  }
}

class ExArg {
  public static long[] time_start;
  public static long[] time_accum;
  static {
     time_start = new long[20];
     time_accum = new long[20];
  };
}

Ramki stosu w tym przykładzie są małe w porównaniu do tego, co zwykle można znaleźć.

Możesz zajrzeć do kodu bajtowego za pomocą javap

javap -c -v -constants JavaException.class

tj. dotyczy metody 4 ...

   protected int bad_method3(int, int) throws JavaException;
flags: ACC_PROTECTED
Code:
  stack=3, locals=3, args_size=3
     0: iload_1       
     1: iconst_3      
     2: if_icmpne     12
     5: aload_0       
     6: iload_1       
     7: iload_2       
     8: invokespecial #6                  // Method thrower:(II)I
    11: pop           
    12: aload_0       
    13: iload_1       
    14: iload_2       
    15: invokespecial #17                 // Method bad_method4:(II)I
    18: ireturn       
  LineNumberTable:
    line 63: 0
    line 64: 12
  StackMapTable: number_of_entries = 1
       frame_type = 12 /* same */

Exceptions:
  throws JavaException
Złupić
źródło
13

Utworzenie śledzenia Exceptionze nullstosem zajmuje tyle samo czasu, co razem throwi try-catchblok. Jednak wypełnienie śladu stosu zajmuje średnio 5 razy dłużej .

Stworzyłem następujący test porównawczy, aby zademonstrować wpływ na wydajność. Dodałem -Djava.compiler=NONEopcję do konfiguracji uruchamiania, aby wyłączyć optymalizację kompilatora. Aby zmierzyć wpływ budowy śladu stosu, rozszerzyłem Exceptionklasę, aby skorzystać z konstruktora bez stosu:

class NoStackException extends Exception{
    public NoStackException() {
        super("",null,false,false);
    }
}

Kod testu porównawczego jest następujący:

public class ExceptionBenchmark {

    private static final int NUM_TRIES = 100000;

    public static void main(String[] args) {

        long throwCatchTime = 0, newExceptionTime = 0, newObjectTime = 0, noStackExceptionTime = 0;

        for (int i = 0; i < 30; i++) {
            throwCatchTime += throwCatchLoop();
            newExceptionTime += newExceptionLoop();
            newObjectTime += newObjectLoop();
            noStackExceptionTime += newNoStackExceptionLoop();
        }

        System.out.println("throwCatchTime = " + throwCatchTime / 30);
        System.out.println("newExceptionTime = " + newExceptionTime / 30);
        System.out.println("newStringTime = " + newObjectTime / 30);
        System.out.println("noStackExceptionTime = " + noStackExceptionTime / 30);

    }

    private static long throwCatchLoop() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {

                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newObjectLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new Object();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newNoStackExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            NoStackException e = new NoStackException();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

}

Wynik:

throwCatchTime = 19
newExceptionTime = 77
newObjectTime = 3
noStackExceptionTime = 15

Oznacza to, że utworzenie a NoStackExceptionjest w przybliżeniu tak drogie, jak wielokrotne rzucanie nim Exception. Pokazuje także, że utworzenie Exceptioni wypełnienie śladu stosu trwa około 4x dłużej.

Austin D.
źródło
1
Czy możesz dodać jeszcze jeden przypadek, w którym utworzysz jedno wystąpienie wyjątku przed czasem rozpoczęcia, a następnie rzucisz go + złap wielokrotnie w pętli? To pokazałoby koszt rzucenia + złapania.
Martin Carney
@MartinCarney Świetna sugestia! Zaktualizowałem swoją odpowiedź, aby to zrobić.
Austin D
Poprawiłem twój kod testowy i wygląda na to, że kompilator dokonuje optymalizacji, która uniemożliwia nam uzyskanie dokładnych liczb.
Martin Carney
@MartinCarney Zaktualizowałem odpowiedź na optymalizację kompilatora rabatów
Austin D
Do Twojej wiadomości, powinieneś prawdopodobnie przeczytać odpowiedzi na pytanie Jak napisać prawidłowy mikro-test porównawczy w Javie? Wskazówka: to nie to.
Daniel Pryden,
4

Ta część pytania ...

Innym sposobem zadawania tego pytania jest to, że jeśli stworzyłem jeden przypadek wyjątku i rzucałem go w kółko, czy byłoby to znacznie szybsze niż tworzenie nowego wyjątku za każdym razem, gdy rzucam?

Wygląda na pytanie, czy utworzenie wyjątku i buforowanie go gdzieś poprawia wydajność. Tak. Jest to to samo, co wyłączenie stosu zapisywanego podczas tworzenia obiektu, ponieważ zostało to już zrobione.

To są czasy, które mam, proszę przeczytać zastrzeżenie po tym ...

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|            193|             251| 77 (%)| 
|   15|            390|             406| 96 (%)| 
|   14|            394|             401| 98 (%)| 
|   13|            381|             385| 99 (%)| 
|   12|            387|             370| 105 (%)| 
|   11|            368|             376| 98 (%)| 
|   10|            188|             192| 98 (%)| 
|    9|            193|             195| 99 (%)| 
|    8|            200|             188| 106 (%)| 
|    7|            187|             184| 102 (%)| 
|    6|            196|             200| 98 (%)| 
|    5|            197|             193| 102 (%)| 
|    4|            198|             190| 104 (%)| 
|    3|            193|             183| 105 (%)| 

Oczywiście problemem jest to, że ślad stosu wskazuje teraz, gdzie utworzono instancję obiektu, a nie skąd został on rzucony.

Złupić
źródło
3

Wykorzystując odpowiedź @ AustinD jako punkt wyjścia, wprowadziłem kilka poprawek. Kod na dole.

Oprócz dodawania przypadku wielokrotnego zgłaszania jednego wystąpienia wyjątku, wyłączyłem również optymalizację kompilatora, aby uzyskać dokładne wyniki wydajności. Dodałem -Djava.compiler=NONEdo argumentów VM, zgodnie z tą odpowiedzią . (W Eclipse edytuj Uruchom konfigurację → Argumenty, aby ustawić ten argument maszyny wirtualnej)

Wyniki:

new Exception + throw/catch = 643.5
new Exception only          = 510.7
throw/catch only            = 115.2
new String (benchmark)      = 669.8

Tak więc utworzenie wyjątku kosztuje około 5 razy więcej niż rzucanie + łapanie go. Zakładając, że kompilator nie optymalizuje dużej części kosztów.

Dla porównania, oto ten sam przebieg testowy bez wyłączania optymalizacji:

new Exception + throw/catch = 382.6
new Exception only          = 379.5
throw/catch only            = 0.3
new String (benchmark)      = 15.6

Kod:

public class ExceptionPerformanceTest {

    private static final int NUM_TRIES = 1000000;

    public static void main(String[] args) {

        double numIterations = 10;

        long exceptionPlusCatchTime = 0, excepTime = 0, strTime = 0, throwTime = 0;

        for (int i = 0; i < numIterations; i++) {
            exceptionPlusCatchTime += exceptionPlusCatchBlock();
            excepTime += createException();
            throwTime += catchBlock();
            strTime += createString();
        }

        System.out.println("new Exception + throw/catch = " + exceptionPlusCatchTime / numIterations);
        System.out.println("new Exception only          = " + excepTime / numIterations);
        System.out.println("throw/catch only            = " + throwTime / numIterations);
        System.out.println("new String (benchmark)      = " + strTime / numIterations);

    }

    private static long exceptionPlusCatchBlock() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw new Exception();
            } catch (Exception e) {
                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long createException() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long createString() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new String("" + i);
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long catchBlock() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {
                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }
}
Martin Carney
źródło
Wyłączanie optymalizacji = świetna technika! Zmodyfikuję moją oryginalną odpowiedź, aby nikogo nie wprowadzić w błąd
Austin D
3
Wyłączenie optymalizacji nie jest lepsze niż napisanie wadliwego testu porównawczego, ponieważ tryb czysto interpretowany nie ma nic wspólnego z rzeczywistą wydajnością. Potęgą JVM jest kompilator JIT, więc po co mierzyć coś, co nie odzwierciedla działania prawdziwej aplikacji?
apangin
2
Jest o wiele więcej aspektów tworzenia, zgłaszania i wyłapywania wyjątków niż w tym „benchmarku”. Zdecydowanie polecam przeczytanie tego postu .
apangin