Frage: Ist die Ausnahmebehandlung in Java tatsächlich langsam?
Konventionelle Erkenntnisse sowie viele Google-Ergebnisse besagen, dass außergewöhnliche Logik nicht für den normalen Programmfluss in Java verwendet werden sollte. Normalerweise werden zwei Gründe angegeben:
- es ist wirklich langsam - sogar eine Größenordnung langsamer als normaler Code (die angegebenen Gründe variieren),
und
- Es ist chaotisch, weil die Leute erwarten, dass nur Fehler in außergewöhnlichem Code behandelt werden.
Diese Frage handelt von # 1.
Als Beispiel beschreibt diese Seite die Behandlung von Java-Ausnahmen als "sehr langsam" und bezieht die Langsamkeit auf die Erstellung der Ausnahmemeldungszeichenfolge - "Diese Zeichenfolge wird dann zum Erstellen des ausgelösten Ausnahmeobjekts verwendet. Dies ist nicht schnell." In dem Artikel Effektive Ausnahmebehandlung in Java heißt es: "Der Grund dafür liegt im Aspekt der Objekterstellung bei der Ausnahmebehandlung, wodurch das Auslösen von Ausnahmen von Natur aus langsam wird." Ein weiterer Grund dafür ist, dass die Stack-Trace-Generierung sie verlangsamt.
Meine Tests (unter Verwendung von Java 1.6.0_07, Java HotSpot 10.0 unter 32-Bit-Linux) haben ergeben, dass die Ausnahmebehandlung nicht langsamer ist als normaler Code. Ich habe versucht, eine Methode in einer Schleife auszuführen, die Code ausführt. Am Ende der Methode verwende ich einen Booleschen Wert, um anzugeben, ob zurückgegeben oder geworfen werden soll . Auf diese Weise ist die tatsächliche Verarbeitung dieselbe. Ich habe versucht, die Methoden in verschiedenen Reihenfolgen auszuführen und meine Testzeiten zu mitteln, da ich dachte, es könnte das Aufwärmen der JVM gewesen sein. In all meinen Tests war der Wurf mindestens so schnell wie die Rückkehr, wenn nicht sogar schneller (bis zu 3,1% schneller). Ich bin völlig offen für die Möglichkeit, dass meine Tests falsch waren, aber ich habe in den letzten ein oder zwei Jahren nichts in Bezug auf das Codebeispiel, Testvergleiche oder Ergebnisse gesehen, die zeigen, dass die Ausnahmebehandlung in Java tatsächlich so ist langsam.
Was mich auf diesen Weg führte, war eine API, die ich verwenden musste und die Ausnahmen als Teil der normalen Steuerlogik auslöste. Ich wollte sie in ihrer Verwendung korrigieren, aber jetzt kann ich es möglicherweise nicht. Muss ich sie stattdessen für ihr vorausschauendes Denken loben?
In dem Artikel Effiziente Java-Ausnahmebehandlung bei der Just-in-Time-Kompilierung schlagen die Autoren vor, dass das Vorhandensein von Ausnahmebehandlungsroutinen allein, auch wenn keine Ausnahmen ausgelöst werden, ausreicht, um zu verhindern, dass der JIT-Compiler den Code ordnungsgemäß optimiert und ihn dadurch verlangsamt . Ich habe diese Theorie noch nicht getestet.
quelle
exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow
- und geben Sie eine vollständige und ausführliche Erklärung, warum. Und er war der Mann, der schrieb Java lib. Daher ist er derjenige, der den API-Vertrag der Klassen definiert. Ich stimme Bill K zu.Antworten:
Es hängt davon ab, wie Ausnahmen implementiert werden. Der einfachste Weg ist die Verwendung von setjmp und longjmp. Das bedeutet, dass alle Register der CPU in den Stapel geschrieben werden (was bereits einige Zeit in Anspruch nimmt) und möglicherweise einige andere Daten erstellt werden müssen ... all dies geschieht bereits in der try-Anweisung. Die throw-Anweisung muss den Stapel abwickeln und die Werte aller Register (und mögliche andere Werte in der VM) wiederherstellen. Try and Throw sind also gleich langsam, und das ist ziemlich langsam. Wenn jedoch keine Ausnahme ausgelöst wird, dauert das Verlassen des Try-Blocks in den meisten Fällen überhaupt nicht (da alles auf den Stapel gelegt wird, der automatisch bereinigt wird, wenn die Methode vorhanden ist).
Sun und andere haben erkannt, dass dies möglicherweise nicht optimal ist und dass VMs mit der Zeit immer schneller werden. Es gibt eine andere Möglichkeit, Ausnahmen zu implementieren, die es ermöglicht, sich blitzschnell zu versuchen (im Allgemeinen passiert überhaupt nichts für Versuche - alles, was passieren muss, ist bereits erledigt, wenn die Klasse von der VM geladen wird) und den Wurf nicht ganz so langsam macht . Ich weiß nicht, welche JVM diese neue, bessere Technik verwendet ...
... aber schreiben Sie in Java, damit Ihr Code später nur auf einer JVM auf einem bestimmten System ausgeführt wird? Denn wenn es jemals auf einer anderen Plattform oder einer anderen JVM-Version (möglicherweise von einem anderen Anbieter) ausgeführt wird, wer sagt, dass sie auch die schnelle Implementierung verwenden? Der schnelle ist komplizierter als der langsame und nicht auf allen Systemen leicht möglich. Sie möchten tragbar bleiben? Dann verlassen Sie sich nicht darauf, dass Ausnahmen schnell sind.
Es macht auch einen großen Unterschied, was Sie innerhalb eines Try-Blocks tun. Wenn Sie einen try-Block öffnen und niemals eine Methode aus diesem try-Block heraus aufrufen, ist der try-Block ultraschnell, da die JIT einen Wurf dann tatsächlich wie ein einfaches goto behandeln kann. Es muss weder den Stapelstatus speichern noch den Stapel abwickeln, wenn eine Ausnahme ausgelöst wird (es muss nur zu den Catch-Handlern gesprungen werden). Dies ist jedoch nicht das, was Sie normalerweise tun. Normalerweise öffnen Sie einen try-Block und rufen dann eine Methode auf, die möglicherweise eine Ausnahme auslöst, oder? Und selbst wenn Sie nur den try-Block in Ihrer Methode verwenden, welche Art von Methode wird dies sein, die keine andere Methode aufruft? Wird es nur eine Zahl berechnen? Wofür brauchen Sie dann Ausnahmen? Es gibt viel elegantere Möglichkeiten, den Programmfluss zu regulieren. Für so ziemlich alles andere als einfache Mathematik,
Siehe folgenden Testcode:
Ergebnis:
Die Verlangsamung des Try-Blocks ist zu gering, um Störfaktoren wie Hintergrundprozesse auszuschließen. Aber der Fangblock hat alles getötet und 66-mal langsamer gemacht!
Wie gesagt, das Ergebnis wird nicht so schlecht sein, wenn Sie try / catch und throw alle innerhalb derselben Methode (method3) setzen, aber dies ist eine spezielle JIT-Optimierung, auf die ich mich nicht verlassen würde. Und selbst wenn diese Optimierung verwendet wird, ist der Wurf immer noch ziemlich langsam. Ich weiß also nicht, was Sie hier versuchen, aber es gibt definitiv einen besseren Weg, als try / catch / throw zu verwenden.
quelle
nanoTime()
benötigt Java 1.5 und ich hatte nur Java 1.4 auf dem System verfügbar, das ich zum Schreiben des obigen Codes verwendet habe. Auch in der Praxis spielt es keine große Rolle. Der einzige Unterschied zwischen den beiden besteht darin, dass eine Nanosekunde die andere Millisekunde ist undnanoTime
nicht durch Taktmanipulationen beeinflusst wird (die irrelevant sind, es sei denn, Sie oder der Systemprozess ändern die Systemuhr genau in dem Moment, in dem der Testcode ausgeführt wird). Im Allgemeinen haben Sie Recht,nanoTime
ist natürlich die bessere Wahl.try
Block, aber neinthrow
. Ihrthrow
Test wirft Ausnahmen 50% der Zeit , es durch das gehttry
. Dies ist eindeutig eine Situation, in der der Fehler nicht außergewöhnlich ist . Wenn Sie dies auf nur 10% reduzieren, wird der Leistungseinbruch massiv verringert. Das Problem bei dieser Art von Test ist, dass die Benutzer dazu ermutigt werden, Ausnahmen überhaupt nicht mehr zu verwenden. Die Verwendung von Ausnahmen für die Ausnahmefallbehandlung ist weitaus besser als das, was Ihr Test zeigt.return
. Es hinterlässt eine Methode irgendwo in der Mitte des Körpers, möglicherweise sogar mitten in einer Operation (die bisher nur zu 50% abgeschlossen wurde), und dercatch
Block kann 20try
Stapelrahmen nach oben sein (eine Methode hat einen Block, der Methode1 aufruft). die Methode2 aufruft, die mehtod3, ... aufruft und in Methode20 mitten in einer Operation eine Ausnahme auslöst). Der Stapel muss 20 Frames nach oben abgewickelt werden, alle nicht abgeschlossenen Vorgänge müssen rückgängig gemacht werden (Vorgänge dürfen nicht zur Hälfte ausgeführt werden) und die CPU-Register müssen sich in einem sauberen Zustand befinden. Das alles kostet Zeit.Zu Ihrer Information, ich habe das Experiment von Mecki erweitert:
Die ersten 3 sind die gleichen wie bei Mecki (mein Laptop ist offensichtlich langsamer).
Methode4 ist identisch mit Methode3, außer dass sie eine erstellt,
new Integer(1)
anstatt dies zu tunthrow new Exception()
.method5 ist wie method3, nur dass es das erstellt,
new Exception()
ohne es zu werfen.method6 ist wie method3, außer dass eine vorab erstellte Ausnahme (eine Instanzvariable) ausgelöst wird, anstatt eine neue zu erstellen.
In Java ist ein Großteil der Kosten für das Auslösen einer Ausnahme die Zeit, die für das Sammeln der Stapelverfolgung aufgewendet wird, die beim Erstellen des Ausnahmeobjekts anfällt. Die tatsächlichen Kosten für das Auslösen der Ausnahme sind zwar hoch, aber erheblich geringer als die Kosten für das Erstellen der Ausnahme.
quelle
Aleksey Shipilëv hat eine sehr gründliche Analyse durchgeführt, in der er Java-Ausnahmen unter verschiedenen Kombinationen von Bedingungen bewertet :
Er vergleicht sie auch mit der Leistung der Überprüfung eines Fehlercodes auf verschiedenen Ebenen der Fehlerhäufigkeit.
Die Schlussfolgerungen (wörtlich aus seinem Beitrag zitiert) waren:
Wirklich außergewöhnliche Ausnahmen sind wunderschön performant. Wenn Sie sie wie vorgesehen verwenden und nur die wirklich außergewöhnlichen Fälle unter der überwältigend großen Anzahl von nicht außergewöhnlichen Fällen kommunizieren, die mit regulärem Code behandelt werden, ist die Verwendung von Ausnahmen der Leistungsgewinn.
Die Leistungskosten von Ausnahmen bestehen aus zwei Hauptkomponenten: der Stapelverfolgungskonstruktion, wenn die Ausnahme instanziiert wird, und dem Abwickeln des Stapels während des Ausnahmewurfs.
Die Kosten für die Stapelverfolgungskonstruktion sind proportional zur Stapeltiefe zum Zeitpunkt der Instanziierung der Ausnahme. Das ist schon schlecht, denn wer auf der Erde kennt die Stapeltiefe, bei der diese Wurfmethode aufgerufen werden würde? Selbst wenn Sie die Stack-Trace-Generierung deaktivieren und / oder die Ausnahmen zwischenspeichern, können Sie nur diesen Teil der Leistungskosten entfernen.
Die Kosten für das Abwickeln des Stapels hängen davon ab, wie viel Glück wir haben, wenn wir den Ausnahmebehandler im kompilierten Code näher bringen. Eine sorgfältige Strukturierung des Codes, um eine gründliche Suche nach Ausnahmebehandlungsroutinen zu vermeiden, hilft uns wahrscheinlich dabei, mehr Glück zu haben.
Sollten wir beide Effekte eliminieren, sind die Leistungskosten für Ausnahmen die der lokalen Niederlassung. Egal wie schön es klingt, das bedeutet nicht, dass Sie Ausnahmen als üblichen Kontrollfluss verwenden sollten, denn in diesem Fall sind Sie der Optimierung des Compilers ausgeliefert! Sie sollten sie nur in wirklich außergewöhnlichen Fällen verwenden, in denen die Ausnahmefrequenz die möglichen unglücklichen Kosten für die Auslösung der tatsächlichen Ausnahme amortisiert .
Die optimistische Faustregel scheint 10 ^ -4 zu sein. Die Häufigkeit für Ausnahmen ist außergewöhnlich genug. Dies hängt natürlich von den Schwergewichten der Ausnahmen selbst, den genauen Aktionen der Ausnahmebehandlungsroutinen usw. ab.
Das Ergebnis ist, dass Sie keine Kosten zahlen, wenn eine Ausnahme nicht ausgelöst wird. Wenn die Ausnahmebedingung ausreichend selten ist, ist die Ausnahmebehandlung schneller als die Verwendung einer Ausnahme
if
. Der vollständige Beitrag ist sehr lesenswert.quelle
Meine Antwort ist leider einfach zu lang, um hier zu posten. Lassen Sie mich hier zusammenfassen und Sie auf http://www.fuwjax.com/how-slow-are-java-exceptions/ verweisen. um die wichtigsten Details zu erfahren.
Die eigentliche Frage lautet hier nicht: "Wie langsam werden" Fehler als Ausnahmen gemeldet "im Vergleich zu" Code, der niemals ausfällt "?" wie die akzeptierte Antwort Sie glauben lassen könnte. Stattdessen sollte die Frage lauten: "Wie langsam werden" Fehler als Ausnahmen gemeldet "im Vergleich zu Fehlern, die auf andere Weise gemeldet wurden?" Im Allgemeinen sind die beiden anderen Möglichkeiten, Fehler zu melden, entweder Sentinel-Werte oder Ergebnis-Wrapper.
Sentinel-Werte sind ein Versuch, eine Klasse im Erfolgsfall und eine andere im Fehlerfall zurückzugeben. Sie können sich fast vorstellen, eine Ausnahme zurückzugeben, anstatt eine auszulösen. Dies erfordert eine gemeinsam genutzte übergeordnete Klasse mit dem Erfolgsobjekt und führt dann eine "Instanz of" -Prüfung und einige Casts durch, um die Erfolgs- oder Fehlerinformationen abzurufen.
Es stellt sich heraus, dass Sentinel-Werte unter dem Risiko der Typensicherheit schneller als Ausnahmen sind, jedoch nur um den Faktor ungefähr 2x. Das mag viel erscheinen, aber 2x deckt nur die Kosten für den Implementierungsunterschied ab. In der Praxis ist der Faktor viel geringer, da unsere Methoden, die möglicherweise fehlschlagen, viel interessanter sind als einige arithmetische Operatoren, wie im Beispielcode an anderer Stelle auf dieser Seite.
Ergebnisverpackungen hingegen opfern die Typensicherheit überhaupt nicht. Sie fassen die Erfolgs- und Misserfolgsinformationen in einer einzigen Klasse zusammen. Anstelle von "instanceof" stellen sie also ein "isSuccess ()" und Getter für die Erfolgs- und Fehlerobjekte bereit. Ergebnisobjekte sind jedoch ungefähr 2x langsamer als die Verwendung von Ausnahmen. Es stellt sich heraus, dass das Erstellen eines neuen Wrapper-Objekts jedes Mal viel teurer ist, als manchmal eine Ausnahme auszulösen.
Darüber hinaus sind Ausnahmen die Sprache, mit der angegeben wird, dass eine Methode möglicherweise fehlschlägt. Es gibt keine andere Möglichkeit, anhand der API zu erkennen, welche Methoden immer (meistens) funktionieren und welche Fehler melden sollen.
Ausnahmen sind sicherer als Sentinels, schneller als Ergebnisobjekte und weniger überraschend als beide. Ich schlage nicht vor, dass try / catch if / else ersetzt, aber Ausnahmen sind der richtige Weg, um Fehler zu melden, selbst in der Geschäftslogik.
Trotzdem möchte ich darauf hinweisen, dass die beiden häufigsten Möglichkeiten, die Leistung, auf die ich gestoßen bin, erheblich zu beeinträchtigen, darin bestehen, unnötige Objekte und verschachtelte Schleifen zu erstellen. Wenn Sie die Wahl haben, eine Ausnahme zu erstellen oder keine Ausnahme zu erstellen, erstellen Sie die Ausnahme nicht. Wenn Sie die Wahl haben, manchmal eine Ausnahme zu erstellen oder ständig ein anderes Objekt zu erstellen, erstellen Sie die Ausnahme.
quelle
Ich habe die Antworten von @Mecki und @incarnate erweitert , ohne Stacktrace-Füllung für Java.
Mit Java 7+ können wir verwenden
Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)
. Aber für Java6 siehe meine Antwort auf diese FrageAusgabe mit Java 1.6.0_45 auf Core i7, 8 GB RAM:
Daher sind Methoden, die Werte zurückgeben, immer noch schneller als Methoden, die Ausnahmen auslösen. IMHO können wir keine klare API entwerfen, die nur Rückgabetypen für Erfolgs- und Fehlerflüsse verwendet. Methoden, die Ausnahmen ohne Stacktrace auslösen, sind 4-5 mal schneller als normale Ausnahmen.
Bearbeiten: NoStackTraceThrowable.java Danke @Greg
quelle
public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
With Java 7+, we can use
aber später hast du geschrieben,Output with Java 1.6.0_45,
also ist dies Java 6 oder 7 Ergebnis?Throwable
Konstruktor verwenden, derboolean writableStackTrace
arg hat. Dies ist jedoch in Java 6 und darunter nicht vorhanden. Aus diesem Grund habe ich eine benutzerdefinierte Implementierung für Java 6 und niedriger angegeben. Der obige Code ist also für Java 6 und niedriger. Bitte lesen Sie die erste Zeile des zweiten Absatzes sorgfältig durch.Optional
oder ähnliches kam etwas spät zu Java. Davor haben wir auch Wrapper-Objekte mit Erfolgs- / Fehlerflags verwendet. Aber es scheint ein bisschen Hacks zu sein und fühlt sich für mich nicht natürlich an.Vor einiger Zeit habe ich eine Klasse geschrieben, um die relative Leistung der Konvertierung von Zeichenfolgen in Ints mit zwei Ansätzen zu testen: (1) Integer.parseInt () aufrufen und die Ausnahme abfangen oder (2) die Zeichenfolge mit einem regulären Ausdruck abgleichen und parseInt () aufrufen Nur wenn das Match erfolgreich ist. Ich habe den regulären Ausdruck so effizient wie möglich verwendet (dh das Erstellen der Pattern- und Matcher-Objekte vor dem Intering der Schleife) und die Stacktraces aus den Ausnahmen nicht gedruckt oder gespeichert.
Bei einer Liste von zehntausend Zeichenfolgen war der parseInt () -Ansatz viermal so schnell wie der Regex-Ansatz, wenn alle gültigen Zahlen waren. Wenn jedoch nur 80% der Zeichenfolgen gültig waren, war der reguläre Ausdruck doppelt so schnell wie parseInt (). Und wenn 20% gültig waren, was bedeutet, dass die Ausnahme in 80% der Fälle ausgelöst und abgefangen wurde, war der reguläre Ausdruck ungefähr zwanzigmal so schnell wie parseInt ().
Ich war von dem Ergebnis überrascht, da der Regex-Ansatz gültige Zeichenfolgen zweimal verarbeitet: einmal für das Match und erneut für parseInt (). Aber Ausnahmen zu werfen und zu fangen machte das mehr als wett. Diese Art von Situation tritt in der realen Welt wahrscheinlich nicht sehr häufig auf, aber wenn dies der Fall ist, sollten Sie auf keinen Fall die Technik zum Ausfangen von Ausnahmen verwenden. Wenn Sie jedoch nur Benutzereingaben oder ähnliches validieren, verwenden Sie auf jeden Fall den parseInt () -Ansatz.
quelle
Integer.ParseInt()
), und ich gehe davon aus, dass die Benutzereingaben meistens korrekt sind. Für meinen Anwendungsfall scheint es also der richtige Weg zu sein, gelegentlich einen Ausnahmetreffer zu verwenden .Ich denke, der erste Artikel bezieht sich auf das Durchlaufen des Aufrufstapels und das Erstellen eines Stack-Trace als den teuren Teil, und während der zweite Artikel dies nicht sagt, denke ich, dass dies der teuerste Teil der Objekterstellung ist. John Rose hat einen Artikel, in dem er verschiedene Techniken beschreibt, um Ausnahmen zu beschleunigen . (Vorabzuweisung und Wiederverwendung einer Ausnahme, Ausnahmen ohne Stapelspuren usw.)
Aber dennoch - ich denke, dies sollte nur als notwendiges Übel betrachtet werden, als letzter Ausweg. Johns Grund dafür ist, Funktionen in anderen Sprachen zu emulieren, die (noch) nicht in der JVM verfügbar sind. Sie sollten sich NICHT angewöhnen, Ausnahmen für den Kontrollfluss zu verwenden. Vor allem nicht aus Leistungsgründen! Wie Sie selbst in # 2 erwähnen, riskieren Sie, schwerwiegende Fehler in Ihrem Code auf diese Weise zu maskieren, und es wird für neue Programmierer schwieriger sein, diese zu warten.
Mikrobenchmarks in Java sind überraschend schwer zu korrigieren (wie mir gesagt wurde), insbesondere wenn Sie in das JIT-Gebiet gelangen. Daher bezweifle ich wirklich, dass die Verwendung von Ausnahmen im wirklichen Leben schneller ist als die "Rückkehr". Ich vermute zum Beispiel, dass Sie in Ihrem Test zwischen 2 und 5 Stapelrahmen haben? Stellen Sie sich nun vor, Ihr Code wird von einer von JBoss bereitgestellten JSF-Komponente aufgerufen. Jetzt haben Sie möglicherweise eine Stapelverfolgung, die mehrere Seiten lang ist.
Vielleicht könnten Sie Ihren Testcode posten?
quelle
Ich weiß nicht, ob diese Themen zusammenhängen, aber ich wollte einmal einen Trick implementieren, der sich auf den Stack-Trace des aktuellen Threads stützt: Ich wollte den Namen der Methode herausfinden, die die Instanziierung innerhalb der instanziierten Klasse ausgelöst hat (ja, die Idee ist verrückt, Ich habe es total aufgegeben). So entdeckte ich , dass Berufung
Thread.currentThread().getStackTrace()
ist extrem langsam (wegen nativendumpThreads
Methode , die es intern verwendet).Java
Throwable
hat dementsprechend eine native MethodefillInStackTrace
. Ich denke, dass der zuvorcatch
beschriebene Killerblock irgendwie die Ausführung dieser Methode auslöst.Aber lassen Sie mich Ihnen eine andere Geschichte erzählen ...
In Scala werden einige Funktionsmerkmale in JVM mithilfe von kompiliert
ControlThrowable
, wodurch es auf folgende Weise erweitertThrowable
und überschriebenfillInStackTrace
wird:Also habe ich den obigen Test angepasst (die Anzahl der Zyklen wird um zehn verringert, meine Maschine ist etwas langsamer :):
Die Ergebnisse sind also:
Sie sehen, der einzige Unterschied zwischen
method3
undmethod4
besteht darin, dass sie verschiedene Arten von Ausnahmen auslösen. Ja,method4
ist immer noch langsamer alsmethod1
undmethod2
, aber der Unterschied ist weitaus akzeptabler.quelle
Ich habe einige Leistungstests mit JVM 1.5 durchgeführt und die Verwendung von Ausnahmen war mindestens zweimal langsamer. Durchschnittlich: Ausführungszeit bei einer trivial kleinen Methode mit Ausnahmen mehr als verdreifacht (3x). Eine trivial kleine Schleife, die die Ausnahme abfangen musste, führte zu einer zweifachen Erhöhung der Selbstzeit.
Ich habe ähnliche Zahlen im Produktionscode sowie in Mikro-Benchmarks gesehen.
Ausnahmen sollten definitiv NICHT für etwas verwendet werden, das häufig aufgerufen wird. Das Werfen von Tausenden von Ausnahmen pro Sekunde würde einen riesigen Flaschenhals verursachen.
Verwenden Sie beispielsweise "Integer.ParseInt (...)", um alle fehlerhaften Werte in einer sehr großen Textdatei zu finden - eine sehr schlechte Idee. (Ich habe gesehen, dass diese Dienstprogrammmethode die Leistung von Produktionscode beeinträchtigt.)
Verwenden einer Ausnahme zum Melden eines fehlerhaften Werts in einem Benutzer-GUI-Formular, vom Leistungsstandpunkt aus wahrscheinlich nicht so schlecht.
Unabhängig davon, ob es sich um eine gute Entwurfspraxis handelt oder nicht, würde ich die Regel befolgen: Wenn der Fehler normal / erwartet ist, verwenden Sie einen Rückgabewert. Wenn es abnormal ist, verwenden Sie eine Ausnahme. Beispiel: Beim Lesen von Benutzereingaben sind fehlerhafte Werte normal. Verwenden Sie einen Fehlercode. Wenn Sie einen Wert an eine interne Dienstprogrammfunktion übergeben, sollten fehlerhafte Werte durch Aufrufen von Code gefiltert werden. Verwenden Sie eine Ausnahme.
quelle
Die Ausnahmeleistung in Java und C # lässt zu wünschen übrig.
Als Programmierer zwingt uns dies, die Regel "Ausnahmen sollten selten verursacht werden" einzuhalten, einfach aus praktischen Leistungsgründen.
Als Informatiker sollten wir uns jedoch gegen diesen problematischen Zustand auflehnen. Die Person, die eine Funktion erstellt, hat häufig keine Ahnung, wie oft sie aufgerufen wird oder ob Erfolg oder Misserfolg wahrscheinlicher sind. Nur der Anrufer hat diese Informationen. Der Versuch, Ausnahmen zu vermeiden, führt zu unklaren API-Idomen, bei denen wir in einigen Fällen nur saubere, aber langsame Ausnahmeversionen haben, in anderen Fällen schnelle, aber klobige Rückgabewertfehler, und in anderen Fällen haben wir beide . Der Bibliotheksimplementierer muss möglicherweise zwei Versionen von APIs schreiben und verwalten, und der Aufrufer muss entscheiden, welche von zwei Versionen in jeder Situation verwendet werden soll.
Das ist eine Art Chaos. Wenn Ausnahmen eine bessere Leistung hätten, könnten wir diese klobigen Redewendungen vermeiden und Ausnahmen verwenden, wie sie verwendet werden sollten ... als strukturierte Fehlerrückgabefunktion.
Ich würde mir wirklich wünschen, dass Ausnahmemechanismen mithilfe von Techniken implementiert werden, die näher an den Rückgabewerten liegen, sodass die Leistung näher an den Rückgabewerten liegt. Dies ist der Punkt, auf den wir in leistungsempfindlichem Code zurückgreifen.
Hier ist ein Codebeispiel, das die Ausnahmeleistung mit der Fehlerrückgabewertleistung vergleicht.
öffentliche Klasse TestIt {
}}
Und hier sind die Ergebnisse:
Das Überprüfen und Weitergeben von Rückgabewerten führt zu einigen Kosten im Vergleich zum Basis-Null-Aufruf, und diese Kosten sind proportional zur Anruftiefe. Bei einer Aufrufkettentiefe von 8 war die Version zur Überprüfung des Fehlerrückgabewerts etwa 27% langsamer als die Basisversion, bei der die Rückgabewerte nicht überprüft wurden.
Die Ausnahmeleistung ist im Vergleich nicht eine Funktion der Anruftiefe, sondern der Ausnahmefrequenz. Die Verschlechterung mit zunehmender Ausnahmefrequenz ist jedoch viel dramatischer. Bei einer Fehlerhäufigkeit von nur 25% lief der Code 24-mal langsamer. Bei einer Fehlerhäufigkeit von 100% ist die Ausnahmeversion fast 100-mal langsamer.
Dies deutet darauf hin, dass wir bei unseren Ausnahmeimplementierungen möglicherweise die falschen Kompromisse eingehen. Ausnahmen können schneller sein, entweder indem kostspielige Stalk-Walks vermieden werden oder indem sie direkt in eine vom Compiler unterstützte Rückgabewertprüfung umgewandelt werden. Bis sie dies tun, vermeiden wir sie, wenn unser Code schnell ausgeführt werden soll.
quelle
HotSpot ist durchaus in der Lage, Ausnahmecode für vom System generierte Ausnahmen zu entfernen, solange alles inline ist. Explizit erstellte Ausnahmen und solche, die ansonsten nicht entfernt wurden, verbringen jedoch viel Zeit mit der Erstellung des Stack-Trace. Überschreiben Sie, um
fillInStackTrace
zu sehen, wie sich dies auf die Leistung auswirken kann.quelle
Auch wenn das Auslösen einer Ausnahme nicht langsam ist, ist es immer noch eine schlechte Idee, Ausnahmen für den normalen Programmfluss auszulösen. Auf diese Weise wird es analog zu einem GOTO ...
Ich denke, das beantwortet die Frage allerdings nicht wirklich. Ich würde mir vorstellen, dass die "konventionelle" Weisheit, Ausnahmen langsam zu werfen, in früheren Java-Versionen (<1.4) zutraf. Zum Erstellen einer Ausnahme muss die VM den gesamten Stack-Trace erstellen. Seitdem hat sich in der VM viel geändert, um die Dinge zu beschleunigen, und dies ist wahrscheinlich ein Bereich, der verbessert wurde.
quelle
break
oderreturn
nicht agoto
.Vergleichen Sie einfach Integer.parseInt mit der folgenden Methode, die bei nicht analysierbaren Daten nur einen Standardwert zurückgibt, anstatt eine Ausnahme auszulösen:
Solange Sie beide Methoden auf "gültige" Daten anwenden, arbeiten beide ungefähr mit derselben Geschwindigkeit (obwohl Integer.parseInt komplexere Daten verarbeiten kann). Sobald Sie jedoch versuchen, ungültige Daten zu analysieren (z. B. "abc" 1.000.000 Mal zu analysieren), sollte der Leistungsunterschied von wesentlicher Bedeutung sein.
quelle
Ein großartiger Beitrag zur Ausnahmeleistung lautet:
https://shipilev.net/blog/2014/exceptional-performance/
Instanziieren oder Wiederverwenden vorhanden, mit und ohne Stapelverfolgung usw.:
Abhängig von der Tiefe der Stapelverfolgung:
Weitere Details (einschließlich x64 Assembler von JIT) finden Sie im Original-Blogbeitrag.
Das bedeutet, dass Hibernate / Spring / etc-EE-shit aufgrund von Ausnahmen (xD) langsam ist und das Umschreiben des App-Steuerungsflusses von Ausnahmen weg (durch
continure
/ ersetzenbreak
undboolean
Flags wie in C vom Methodenaufruf zurückgeben) die Leistung Ihrer Anwendung 10x-100x verbessert , je nachdem wie oft du sie wirfst))quelle
Ich habe die Antwort von @Mecki oben so geändert, dass Methode1 einen Booleschen Wert und eine Überprüfung der aufrufenden Methode zurückgibt, da Sie eine Ausnahme nicht einfach durch nichts ersetzen können. Nach zwei Läufen war Methode1 entweder immer noch die schnellste oder so schnell wie Methode2.
Hier ist eine Momentaufnahme des Codes:
und Ergebnisse:
Führen Sie 1 aus
Führen Sie 2 aus
quelle
Eine Ausnahme ist nur für die Behandlung unerwarteter Bedingungen zur Laufzeit vorgesehen .
Die Verwendung einer Ausnahme anstelle einer einfachen Validierung, die in der Kompilierungszeit durchgeführt werden kann, verzögert die Validierung bis zur Ausführungszeit. Dies wird wiederum die Effizienz des Programms verringern.
Das Auslösen einer Ausnahme anstelle einer einfachen if..else- Validierung macht das Schreiben und Verwalten des Codes ebenfalls komplex.
quelle
Meine Meinung zur Ausnahmegeschwindigkeit im Vergleich zur programmgesteuerten Überprüfung von Daten.
Viele Klassen hatten einen String-zu-Wert-Konverter (Scanner / Parser), angesehene und bekannte Bibliotheken;)
hat normalerweise Form
Der Name der Ausnahme ist nur ein Beispiel, normalerweise ist er nicht aktiviert (Laufzeit), daher ist die Auslösedeklaration nur mein Bild
manchmal existieren zweite Form:
niemals werfen
Wenn der zweite Ins nicht verfügbar ist (oder der Programmierer zu wenig Dokumente liest und nur den ersten verwendet), schreiben Sie diesen Code mit regulären Ausdrücken. Reguläre Äußerungen sind cool, politisch korrekt usw.:
Mit diesem Code haben Programmierer keine Kosten für Ausnahmen. ABER HAT IMMER vergleichsweise sehr hohe Kosten für reguläre Ausdrücke im Vergleich zu kleinen Kosten für Ausnahmen manchmal.
Ich benutze fast immer in einem solchen Kontext
Ohne Stacktrace usw. zu analysieren, glaube ich nach Vorträgen von Yours ziemlich schnell.
Hab keine Angst vor Ausnahmen
quelle
Warum sollten Ausnahmen langsamer sein als normale Renditen?
Solange Sie den Stacktrace nicht auf dem Terminal drucken, in einer Datei oder ähnlichem speichern, erledigt der catch-Block nicht mehr Arbeit als andere Codeblöcke. Ich kann mir also nicht vorstellen, warum "throw new my_cool_error ()" so langsam sein sollte.
Gute Frage und ich freue mich auf weitere Informationen zu diesem Thema!
quelle