Die Leistung von Ausnahmen in Java und C# lässt viel zu wünschen übrig.
Als Programmierer zwingt uns dies dazu, nach der Regel "Ausnahmen sollten selten verursacht werden" zu leben, einfach aus praktischen Leistungsgründen.
Als Informatiker sollten wir uns jedoch gegen diesen problematischen Zustand auflehnen. Die Person, die eine Funktion schreibt, hat oft keine Ahnung, wie oft sie aufgerufen wird, oder ob Erfolg oder Misserfolg wahrscheinlicher ist. Nur der Aufrufer 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 und in anderen Fällen schnelle, aber schwerfällige Rückgabefehler haben, und in wieder anderen Fällen haben wir am Ende beides. Der Bibliotheksimplementierer muss möglicherweise zwei Versionen von APIs schreiben und pflegen, und der Aufrufer muss entscheiden, welche der beiden Versionen er in jeder Situation verwenden will.
Das ist ein ziemliches Durcheinander. Wenn Ausnahmen eine bessere Leistung hätten, könnten wir diese klobigen Idiome vermeiden und Ausnahmen so verwenden, wie sie gedacht sind... als strukturierte Fehlerrückgabe.
Ich würde wirklich gerne sehen, dass Ausnahmemechanismen mit Techniken implementiert werden, die näher an den Rückgabewerten liegen, so dass wir die Leistung näher an den Rückgabewerten haben könnten da dies das ist, worauf wir bei leistungsempfindlichem Code zurückgreifen.
Hier ist ein Code-Beispiel, das die Leistung von Ausnahmen mit der Leistung von Fehler-Rückgabewerten vergleicht.
public class TestIt {
int value;
public int getValue() {
return value;
}
public void reset() {
value = 0;
}
public boolean baseline_null(boolean shouldfail, int recurse_depth) {
if (recurse_depth <= 0) {
return shouldfail;
} else {
return baseline_null(shouldfail,recurse_depth-1);
}
}
public boolean retval_error(boolean shouldfail, int recurse_depth) {
if (recurse_depth <= 0) {
if (shouldfail) {
return false;
} else {
return true;
}
} else {
boolean nested_error = retval_error(shouldfail,recurse_depth-1);
if (nested_error) {
return true;
} else {
return false;
}
}
}
public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
if (recurse_depth <= 0) {
if (shouldfail) {
throw new Exception();
}
} else {
exception_error(shouldfail,recurse_depth-1);
}
}
public static void main(String[] args) {
int i;
long l;
TestIt t = new TestIt();
int failures;
int ITERATION_COUNT = 100000000;
// (0) baseline null workload
for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {
int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);
failures = 0;
long start_time = System.currentTimeMillis();
t.reset();
for (i = 1; i < ITERATION_COUNT; i++) {
boolean shoulderror = (i % EXCEPTION_MOD) == 0;
t.baseline_null(shoulderror,recurse_depth);
}
long elapsed_time = System.currentTimeMillis() - start_time;
System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
recurse_depth, exception_freq, failures,elapsed_time);
}
}
// (1) retval_error
for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {
int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);
failures = 0;
long start_time = System.currentTimeMillis();
t.reset();
for (i = 1; i < ITERATION_COUNT; i++) {
boolean shoulderror = (i % EXCEPTION_MOD) == 0;
if (!t.retval_error(shoulderror,recurse_depth)) {
failures++;
}
}
long elapsed_time = System.currentTimeMillis() - start_time;
System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
recurse_depth, exception_freq, failures,elapsed_time);
}
}
// (2) exception_error
for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {
int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);
failures = 0;
long start_time = System.currentTimeMillis();
t.reset();
for (i = 1; i < ITERATION_COUNT; i++) {
boolean shoulderror = (i % EXCEPTION_MOD) == 0;
try {
t.exception_error(shoulderror,recurse_depth);
} catch (Exception e) {
failures++;
}
}
long elapsed_time = System.currentTimeMillis() - start_time;
System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
recurse_depth, exception_freq, failures,elapsed_time);
}
}
}
}
Und hier sind die Ergebnisse:
baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141 ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367 ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms
Die Überprüfung und Weitergabe von Rückgabewerten verursacht zusätzliche Kosten im Vergleich zum Baseline-Null-Aufruf, und diese Kosten sind proportional zur Aufruf-Tiefe. Bei einer Aufrufkettentiefe von 8 war die Version mit Fehler-Rückgabewert-Prüfung etwa 27 % langsamer als die Basisversion, bei der die Rückgabewerte nicht geprüft wurden.
Die Leistung bei Ausnahmen hängt dagegen nicht von der Aufruftiefe ab, sondern von der Häufigkeit der Ausnahmen. Die Verschlechterung mit zunehmender Ausnahmehäufigkeit ist jedoch viel dramatischer. Bei einer Fehlerhäufigkeit von nur 25 % lief der Code 24 TIMES langsamer. Bei einer Fehlerhäufigkeit von 100 % ist die Ausnahmeversion fast 100 TIMES langsamer.
Das deutet für mich darauf hin, dass wir vielleicht die falschen Kompromisse bei unseren Ausnahmeimplementierungen eingehen. Ausnahmen könnten schneller sein, entweder durch die Vermeidung von kostspieligen Stalking-Walks oder durch die Umwandlung in eine compilerunterstützte Rückgabewertüberprüfung. Solange das nicht der Fall ist, müssen wir sie meiden, wenn wir wollen, dass unser Code schnell läuft.
13 Stimmen
Ich weiß, dass Sie nicht nach 2) gefragt haben, aber Sie sollten wirklich erkennen, dass die Verwendung einer Ausnahme für den Programmablauf nicht besser ist als die Verwendung von GOTOs. Einige Leute verteidigen Gotos, andere würden das verteidigen, wovon Sie sprechen, aber wenn Sie jemanden fragen, der beides über einen längeren Zeitraum implementiert und gewartet hat, wird er Ihnen sagen, dass beides schlechte, schwer zu wartende Entwurfspraktiken sind (und er wird wahrscheinlich den Namen der Person verfluchen, die dachte, sie sei klug genug, um die Entscheidung zu treffen, sie zu verwenden).
98 Stimmen
Bill, die Behauptung, die Verwendung von Ausnahmen für den Programmablauf sei nicht besser als die Verwendung von GOTOs, ist nicht besser als die Behauptung, die Verwendung von Konditionalen und Schleifen für den Programmablauf sei nicht besser als die Verwendung von GOTOs. Das ist ein Ablenkungsmanöver. Erkläre dich selbst. Ausnahmen können und werden in anderen Sprachen effektiv für den Programmfluss verwendet. Idiomatischer Python-Code verwendet zum Beispiel regelmäßig Ausnahmen. Ich kann und habe Code gepflegt, der Ausnahmen auf diese Weise verwendet (allerdings nicht in Java), und ich glaube nicht, dass daran etwas grundsätzlich falsch ist.
5 Stimmen
Beachten Sie, dass einige Web-Frameworks Exceptions als bequeme Möglichkeit zur Umleitung verwenden - z.B. Wicket's RestartResponseException . Es passiert nur wenige Male pro Anfrage, normalerweise nicht, und ich kann mir kaum einen bequemeren Weg in einem Java-orientierten Komponenten-Framework vorstellen.
24 Stimmen
@mmalone Ausnahmen für den normalen Kontrollfluss zu verwenden, ist in Java eine schlechte Idee, weil die Wahl des Paradigmas wurde auf diese Weise getroffen . Lesen Sie Bloch EJ2 - er sagt eindeutig, dass, Zitat, (Punkt 57)
exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow
- mit einer vollständigen und ausführlichen Erklärung, warum. Und er war derjenige, der schrieb Java lib. Daher ist er derjenige, der den API-Vertrag der Klassen definiert. /Stimme Bill K in diesem Punkt zu.8 Stimmen
@OndraŽižka Wenn ein Framework dies tut (Verwendung von Exceptions unter nicht außergewöhnlichen Bedingungen), ist es fehlerhaft und vom Design her gebrochen und bricht den Vertrag der Exception-Klasse der Sprache. Nur weil einige Leute lausigen Code schreiben, ist er nicht weniger lausig.
7 Stimmen
Btw, es beunruhigt mich sehr, dass der Kommentar von mmalone so viele "upvotes" bekommen hat, obwohl er sagte: "Ich glaube nicht, dass daran etwas grundsätzlich falsch ist"... Ich verstehe zwar die Bitte um einen Verweis, die er geäußert hat (obwohl der Verweis in diesem Fall ziemlich offensichtlich ist), aber es macht mich traurig zu sehen, wie viele Leute dem zustimmen, da es für mich nur unterstreicht, wie sehr sich die SO "Qualitätssicherung der Gemeinschaft" im Laufe der Zeit verschlechtert hat.
2 Stimmen
Kein Geringerer als der Schöpfer von stackoverflow.com hat zu Protokoll gegeben, dass Ausnahmen schlimmer sind als GOTOs: joelonsoftware.com/artikel/2003/10/13.html
11 Stimmen
Kein anderer als der Schöpfer von stackoverflow.com irrt sich bei Ausnahmen. Die goldene Regel der Softwareentwicklung lautet: Mache niemals das Einfache komplex und unhandlich. Er schreibt: "Es ist wahr, dass ein einfaches 3-zeiliges Programm oft auf 48 Zeilen anwächst, wenn man eine gute Fehlerprüfung einbaut, aber so ist das Leben, ..." Dies ist eine Suche nach Reinheit, nicht nach Einfachheit.
0 Stimmen
Eines der lustigen Dinge an Java - wir machen uns Gedanken über so etwas wie die Leistung von try/catch, die Java um einen RIESIGEN Faktor verlangsamt - aber es wäre immer noch schneller als fast jede andere Sprache da draußen (abgesehen von C und ein paar esoterischen Sprachen wie Fortran!?!). Selbst wenn Sie viel mit Ausnahmen arbeiten würden, würden Sie nicht auf die Geschwindigkeit von Python herunterkommen; und was Ruby angeht??? Nicht einmal, wenn jede einzelne Anweisung irgendwie ausnahmebasiert wäre, könnte man so langsam werden. Andererseits führen Ausnahmen zu schwer nachvollziehbarem Code und sind generell eine PITA, die man am besten vermeiden sollte, wo immer es möglich ist.
0 Stimmen
Früher war das Auffüllen der Stacktrace ziemlich langsam. Meinem Verständnis nach verbesserte sich dies drastisch um Java 5-6-ish
0 Stimmen
Der Operator sollte einige Tests durchführen, nachdem er tausend Frames auf den Stack geschoben hat, damit die Auswirkungen eines größeren Stacktraces deutlicher werden.
0 Stimmen
Die Frage bezieht sich auf die "Behandlung" von Ausnahmen, bei denen die Alternative sehr oft ein Absturz ist. Viel langsamer als angehalten kann man nicht werden. Wenn Sie Ausnahmen als Element der Ablaufsteuerung verwenden wollen, wie Bill K. vorschlug, stellen Sie einfach sicher, dass niemand sonst den Code pflegen muss...