Welcher Teil des Auslösens einer Ausnahme ist teuer?

256

In Java ist die Verwendung von throw / catch als Teil der Logik, wenn tatsächlich kein Fehler vorliegt, im Allgemeinen (teilweise) eine schlechte Idee, da das Auslösen und Abfangen einer Ausnahme teuer ist und das häufige Ausführen in einer Schleife in der Regel weitaus langsamer ist als bei anderen Kontrollstrukturen, bei denen keine Ausnahmen ausgelöst werden.

Meine Frage ist, sind die Kosten, die beim Werfen / Fangen selbst oder beim Erstellen des Ausnahmeobjekts anfallen (da es viele Laufzeitinformationen einschließlich des Ausführungsstapels erhält)?

Mit anderen Worten, wenn ich es tue

Exception e = new Exception();

Aber werfen Sie es nicht, ist das der größte Teil der Kosten für das Werfen, oder ist die Handhabung von Wurf + Fang teuer?

Ich frage nicht, ob das Einfügen von Code in einen Try / Catch-Block die Kosten für die Ausführung dieses Codes erhöht, sondern ob das Abfangen der Ausnahme der teure Teil oder das Erstellen (Aufrufen des Konstruktors für) der Ausnahme der teure Teil ist .

Eine andere Möglichkeit, dies zu fragen, ist, wenn ich eine Instanz von Exception erstellt und sie immer wieder geworfen und abgefangen hätte, wäre dies erheblich schneller als das Erstellen einer neuen Exception bei jedem Werfen?

Martin Carney
quelle
20
Ich glaube, es füllt die Stapelspur aus und füllt sie.
Elliott Frisch
12
Überprüfen Sie dies: stackoverflow.com/questions/16451777/…
Jorge
"Wenn ich eine Instanz von Exception erstellt und sie immer wieder geworfen und abgefangen habe", wird beim Erstellen einer Ausnahme die Stapelspur gefüllt, was bedeutet, dass sie unabhängig vom Ort, von dem sie geworfen wurde, immer dieselbe Stactrace ist. Wenn Stacktrace für Sie nicht wichtig ist, können Sie Ihre Idee ausprobieren. Dies kann jedoch das Debuggen in einigen Fällen sehr schwierig oder gar unmöglich machen.
Pshemo
2
@Pshemo Ich habe nicht vor, dies tatsächlich im Code zu tun, ich frage nach der Leistung und verwende diese Absurdität als Beispiel, wo es einen Unterschied machen könnte.
Martin Carney
@MartinCarney Ich habe Ihrem letzten Absatz eine Antwort hinzugefügt, dh das Zwischenspeichern einer Ausnahme hätte einen Leistungsgewinn. Wenn es nützlich ist, kann ich den Code hinzufügen, wenn nicht, kann ich die Antwort löschen.
Harry

Antworten:

267

Das Erstellen eines Ausnahmeobjekts ist nicht teurer als das Erstellen anderer regulärer Objekte. Die Hauptkosten sind in der nativen fillInStackTraceMethode verborgen , die den Aufrufstapel durchläuft und alle erforderlichen Informationen zum Erstellen einer Stapelverfolgung sammelt: Klassen, Methodennamen, Zeilennummern usw.

Der Mythos über hohe Ausnahmekosten beruht auf der Tatsache, dass die meisten ThrowableKonstruktoren implizit aufrufen fillInStackTrace. Es gibt jedoch einen Konstruktor zum Erstellen eines ThrowableTrace ohne Stack. Sie können damit Wurfgegenstände erstellen, die sehr schnell instanziiert werden können. Eine andere Möglichkeit, leichte Ausnahmen zu erstellen, besteht darin, sie zu überschreiben fillInStackTrace.


Was ist nun mit einer Ausnahme?
Tatsächlich hängt es davon ab, wo eine ausgelöste Ausnahme abgefangen wird .

Wenn es in derselben Methode (oder genauer gesagt im selben Kontext, da der Kontext aufgrund von Inlining mehrere Methoden enthalten kann) abgefangen wird, throwist es so schnell und einfach wie goto(natürlich nach der JIT-Kompilierung).

Befindet sich ein catchBlock jedoch irgendwo tiefer im Stapel, muss JVM die Stapelrahmen abwickeln. Dies kann erheblich länger dauern. Es dauert sogar noch länger, wenn synchronizedBlöcke oder Methoden beteiligt sind, da beim Abwickeln Monitore freigegeben werden, die entfernten Stapelrahmen gehören.


Ich könnte die obigen Aussagen durch geeignete Benchmarks bestätigen, aber zum Glück muss ich dies nicht tun, da alle Aspekte bereits in der Stelle von Alexey Shipilev, dem Performance-Ingenieur von HotSpot, perfekt behandelt sind: Die außergewöhnliche Leistung von Lil 'Exception .

Apangin
quelle
8
Wie im Artikel erwähnt und hier angesprochen, ist das Ergebnis, dass die Kosten für das Auslösen / Abfangen von Ausnahmen stark von der Tiefe der Anrufe abhängen. Der Punkt hier ist, dass die Aussage "Ausnahmen sind teuer" nicht wirklich richtig ist. Eine korrektere Aussage ist, dass Ausnahmen teuer sein können. Ehrlich gesagt denke ich, dass es zu stark formuliert ist, nur Ausnahmen für "wirklich außergewöhnliche Fälle" (wie im Artikel) zu verwenden. Sie eignen sich perfekt für so ziemlich alles außerhalb des normalen Rückflusses und es ist schwierig, die Auswirkungen ihrer Verwendung auf diese Weise in einer realen Anwendung auf die Leistung zu erkennen.
JimmyJames
14
Es könnte sich lohnen, den Overhead von Ausnahmen zu quantifizieren. Selbst im schlimmsten Fall, über den in diesem ziemlich ausführlichen Artikel berichtet wird (das Auslösen und Abfangen einer dynamischen Ausnahme mit einem tatsächlich abgefragten Stacktrace mit einer Tiefe von 1000 Stack-Frames), dauert es 80 Mikrosekunden. Dies kann von Bedeutung sein, wenn Ihr System Tausende von Ausnahmen pro Sekunde verarbeiten muss, ansonsten aber keine Sorge wert ist. Und das ist der schlimmste Fall; Wenn Ihre Stacktraces etwas vernünftiger sind oder Sie sie nicht abfragen, können wir fast eine Million Ausnahmen pro Sekunde verarbeiten.
Meriton
13
Ich betone dies, weil viele Leute, wenn sie lesen, dass Ausnahmen "teuer" sind, nie aufhören, "teuer im Vergleich zu was" zu fragen, sondern davon ausgehen, dass sie "teurer Teil ihres Programms" sind, was sie sehr selten sind.
Meriton
2
Es gibt einen Teil, der hier nicht erwähnt wird: die potenziellen Kosten für die Verhinderung der Anwendung von Optimierungen. Ein extremes Beispiel wäre die JVM, die nicht inliniert, um "verwirrende" Stapelspuren zu vermeiden, aber ich habe (Mikro-) Benchmarks gesehen, bei denen das Vorhandensein oder Fehlen von Ausnahmen zuvor Optimierungen in C ++ vorgenommen oder unterbrochen hat.
Matthieu M.
3
@MatthieuM. Ausnahmen und Try / Catch-Blöcke verhindern nicht, dass JVM Inlining ausführt. Für kompilierte Methoden werden reale Stapelspuren aus einer virtuellen Stapelrahmentabelle rekonstruiert, die als Metadaten gespeichert ist. Ich kann mich nicht an eine JIT-Optimierung erinnern, die nicht mit try / catch kompatibel ist. Die Try / Catch-Struktur selbst fügt dem Methodencode nichts hinzu, sondern existiert nur als Ausnahmetabelle neben dem Code.
Apangin
72

Die erste Operation in den meisten ThrowableKonstruktoren besteht darin , die Stapelverfolgung auszufüllen, in der der größte Teil der Kosten anfällt.

Es gibt jedoch einen geschützten Konstruktor mit einem Flag zum Deaktivieren der Stapelverfolgung. Auf diesen Konstruktor kann auch beim Erweitern Exceptionzugegriffen werden. Wenn Sie einen benutzerdefinierten Ausnahmetyp erstellen, können Sie die Erstellung von Stapelablaufverfolgungen vermeiden und auf Kosten weniger Informationen eine bessere Leistung erzielen.

Wenn Sie eine einzelne Ausnahme eines beliebigen Typs auf normale Weise erstellen, können Sie sie viele Male erneut auslösen, ohne den Aufwand für das Ausfüllen des Stack-Trace. Die Stapelverfolgung gibt jedoch an, wo sie erstellt wurde und nicht, wo sie in einer bestimmten Instanz ausgelöst wurde.

Aktuelle Versionen von Java machen einige Versuche, die Erstellung von Stack-Trace zu optimieren. Native Code wird aufgerufen, um den Stack-Trace auszufüllen, der den Trace in einer leichteren nativen Struktur aufzeichnet. Entsprechende Java - StackTraceElementObjekte werden träge aus diesem Datensatz nur dann erstellt , wenn die getStackTrace(), printStackTrace()oder anderen Methoden, die die Spur erfordern , werden genannt.

Wenn Sie die Erzeugung von Stapelspuren eliminieren, sind die anderen Hauptkosten das Abwickeln des Stapels zwischen dem Wurf und dem Fang. Je weniger intervenierende Frames auftreten, bevor die Ausnahme abgefangen wird, desto schneller ist dies.

Entwerfen Sie Ihr Programm so, dass Ausnahmen nur in wirklich außergewöhnlichen Fällen ausgelöst werden und Optimierungen wie diese schwer zu rechtfertigen sind.

erickson
quelle
25

Hier gibt es eine gute Beschreibung der Ausnahmen.

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

Die Schlussfolgerung ist, dass die Stapelspurkonstruktion und das Abwickeln des Stapels die teuren Teile sind. Der folgende Code nutzt eine Funktion, 1.7mit der wir Stapelspuren ein- und ausschalten können. Wir können dies dann verwenden, um zu sehen, welche Kosten verschiedene Szenarien haben

Das Folgende sind Zeitpunkte für die Objekterstellung allein. Ich habe Stringhier hinzugefügt , damit Sie sehen können, dass es fast keinen Unterschied gibt, ein JavaExceptionObjekt und ein Objekt zu erstellen, ohne dass der Stapel geschrieben wird String. Bei eingeschaltetem Stapelschreiben ist der Unterschied dramatisch, dh mindestens eine Größenordnung langsamer.

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)

Das Folgende zeigt, wie lange es gedauert hat, millionenfach von einem Wurf in einer bestimmten Tiefe zurückzukehren.

|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 (%)| 

Das Folgende ist mit ziemlicher Sicherheit eine grobe Vereinfachung ...

Wenn wir bei aktiviertem Stack-Schreiben eine Tiefe von 16 annehmen, dauert die Objekterstellung ungefähr 40% der Zeit. Die eigentliche Stapelverfolgung macht den größten Teil davon aus. ~ 93% der Instanziierung des JavaException-Objekts ist auf die Stapelverfolgung zurückzuführen. Dies bedeutet, dass das Abwickeln des Stapels in diesem Fall die anderen 50% der Zeit in Anspruch nimmt.

Wenn wir die Stapelverfolgungsobjekterstellung deaktivieren, macht dies einen viel kleineren Anteil aus, dh 20%, und das Abwickeln des Stapels macht jetzt 80% der Zeit aus.

In beiden Fällen nimmt das Abwickeln des Stapels einen großen Teil der Gesamtzeit in Anspruch.

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

Die Stapelrahmen in diesem Beispiel sind winzig im Vergleich zu dem, was Sie normalerweise finden würden.

Sie können den Bytecode mit javap einsehen

javap -c -v -constants JavaException.class

dh dies ist für Methode 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
Harry
quelle
13

Die Erstellung der Exceptionmit einem nullStack-Trace dauert ungefähr so ​​lange wie die throwund try-catchblock zusammen. Das Füllen des Stack-Trace dauert jedoch durchschnittlich 5x länger .

Ich habe den folgenden Benchmark erstellt, um die Auswirkungen auf die Leistung zu demonstrieren. Ich habe das -Djava.compiler=NONEzur Run-Konfiguration hinzugefügt , um die Compiler-Optimierung zu deaktivieren. Um die Auswirkungen des Erstellens der Stapelverfolgung zu messen, habe ich die ExceptionKlasse erweitert, um den stapelfreien Konstruktor zu nutzen:

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

Der Benchmark-Code lautet wie folgt:

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

}

Ausgabe:

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

Dies impliziert, dass das Erstellen eines a NoStackExceptionungefähr so ​​teuer ist wie das wiederholte Werfen desselben Exception. Es zeigt auch, dass das Erstellen Exceptionund Füllen des Stack-Trace ungefähr viermal länger dauert .

Austin D.
quelle
1
Könnten Sie einen weiteren Fall hinzufügen, in dem Sie vor der Startzeit eine Ausnahmeinstanz erstellen und diese dann wiederholt in einer Schleife werfen + abfangen? Das würde die Kosten für das Werfen + Fangen zeigen.
Martin Carney
@ MartinCarney Toller Vorschlag! Ich habe meine Antwort aktualisiert, um genau das zu tun.
Austin D
Ich habe Ihren Testcode optimiert, und es sieht so aus, als würde der Compiler einige Optimierungen vornehmen, die verhindern, dass wir genaue Zahlen erhalten.
Martin Carney
@ MartinCarney Ich habe die Antwort auf Discount Compiler Optimierung aktualisiert
Austin D
Zu Ihrer Information, Sie sollten wahrscheinlich die Antworten auf Wie schreibe ich einen korrekten Mikro-Benchmark in Java? Hinweis: Das ist es nicht.
Daniel Pryden
4

Dieser Teil der Frage ...

Eine andere Möglichkeit, dies zu fragen, ist, wenn ich eine Instanz von Exception erstellt und sie immer wieder geworfen und abgefangen hätte, wäre dies erheblich schneller als das Erstellen einer neuen Exception bei jedem Werfen?

Scheint zu fragen, ob das Erstellen und Zwischenspeichern einer Ausnahme die Leistung verbessert. Ja tut es. Dies entspricht dem Deaktivieren des Stapels, der bei der Objekterstellung geschrieben wird, da dies bereits geschehen ist.

Dies sind die Zeiten, die ich habe, bitte lesen Sie den Vorbehalt danach ...

|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 (%)| 

Das Problem dabei ist natürlich, dass Ihre Stapelverfolgung jetzt darauf verweist, wo Sie das Objekt instanziiert haben und nicht darauf, woher es geworfen wurde.

Harry
quelle
3

Mit der Antwort von @ AustinD als Ausgangspunkt habe ich einige Verbesserungen vorgenommen. Code unten.

Zusätzlich zum Hinzufügen des Falls, in dem eine Ausnahmeinstanz wiederholt ausgelöst wird, habe ich auch die Compileroptimierung deaktiviert, damit wir genaue Leistungsergebnisse erhalten. Ich -Djava.compiler=NONEhabe den VM-Argumenten gemäß dieser Antwort hinzugefügt . (Bearbeiten Sie in Eclipse die Option Konfiguration ausführen → Argumente, um dieses VM-Argument festzulegen.)

Die Ergebnisse:

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

Das Erstellen der Ausnahme kostet also ungefähr das Fünffache des Werfens + Fangens. Angenommen, der Compiler optimiert nicht viel von den Kosten.

Zum Vergleich hier der gleiche Testlauf ohne Deaktivierung der Optimierung:

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

Code:

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
quelle
Optimierung deaktivieren = großartige Technik! Ich werde meine ursprüngliche Antwort bearbeiten, um niemanden irrezuführen
Austin D
3
Das Deaktivieren der Optimierung ist keineswegs besser als das Schreiben eines fehlerhaften Benchmarks, da der rein interpretierte Modus nichts mit der tatsächlichen Leistung zu tun hat. Die Stärke von JVM ist der JIT-Compiler. Was bringt es also, etwas zu messen, das nicht die Funktionsweise realer Anwendungen widerspiegelt?
Apangin
2
Es gibt viel mehr Aspekte beim Erstellen, Werfen und Fangen von Ausnahmen als bei diesem „Benchmark“. Ich empfehle Ihnen dringend, diesen Beitrag zu lesen .
Apangin