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?
quelle
Antworten:
Das Erstellen eines Ausnahmeobjekts ist nicht teurer als das Erstellen anderer regulärer Objekte. Die Hauptkosten sind in der nativen
fillInStackTrace
Methode 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
Throwable
Konstruktoren implizit aufrufenfillInStackTrace
. Es gibt jedoch einen Konstruktor zum Erstellen einesThrowable
Trace 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 überschreibenfillInStackTrace
.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,
throw
ist es so schnell und einfach wiegoto
(natürlich nach der JIT-Kompilierung).Befindet sich ein
catch
Block jedoch irgendwo tiefer im Stapel, muss JVM die Stapelrahmen abwickeln. Dies kann erheblich länger dauern. Es dauert sogar noch länger, wennsynchronized
Blö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 .
quelle
Die erste Operation in den meisten
Throwable
Konstruktoren 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
Exception
zugegriffen 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 -
StackTraceElement
Objekte werden träge aus diesem Datensatz nur dann erstellt , wenn diegetStackTrace()
,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.
quelle
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.7
mit der wir Stapelspuren ein- und ausschalten können. Wir können dies dann verwenden, um zu sehen, welche Kosten verschiedene Szenarien habenDas Folgende sind Zeitpunkte für die Objekterstellung allein. Ich habe
String
hier hinzugefügt , damit Sie sehen können, dass es fast keinen Unterschied gibt, einJavaException
Objekt und ein Objekt zu erstellen, ohne dass der Stapel geschrieben wirdString
. Bei eingeschaltetem Stapelschreiben ist der Unterschied dramatisch, dh mindestens eine Größenordnung langsamer.Das Folgende zeigt, wie lange es gedauert hat, millionenfach von einem Wurf in einer bestimmten Tiefe zurückzukehren.
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.
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
dh dies ist für Methode 4 ...
quelle
Die Erstellung der
Exception
mit einemnull
Stack-Trace dauert ungefähr so lange wie diethrow
undtry-catch
block 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=NONE
zur Run-Konfiguration hinzugefügt , um die Compiler-Optimierung zu deaktivieren. Um die Auswirkungen des Erstellens der Stapelverfolgung zu messen, habe ich dieException
Klasse erweitert, um den stapelfreien Konstruktor zu nutzen:Der Benchmark-Code lautet wie folgt:
Ausgabe:
Dies impliziert, dass das Erstellen eines a
NoStackException
ungefähr so teuer ist wie das wiederholte Werfen desselbenException
. Es zeigt auch, dass das ErstellenException
und Füllen des Stack-Trace ungefähr viermal länger dauert .quelle
Dieser Teil der Frage ...
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 ...
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.
quelle
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=NONE
habe 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:
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:
Code:
quelle