Ich sehe immer wieder Leute sagen, dass Ausnahmen langsam sind, aber ich sehe nie einen Beweis. Anstatt zu fragen, ob dies der Fall ist, werde ich fragen, wie Ausnahmen hinter den Kulissen funktionieren, damit ich entscheiden kann, wann sie verwendet werden sollen und ob sie langsam sind.
Soweit ich weiß, sind Ausnahmen die gleichen wie eine mehrfache Rückgabe, mit der Ausnahme, dass nach jeder Rückgabe auch geprüft wird, ob eine weitere Rückgabe erforderlich ist oder gestoppt werden muss. Wie wird überprüft, wann die Rückkehr beendet werden soll? Ich denke, es gibt einen zweiten Stapel, der den Typ der Ausnahme und einen Stapelspeicherort enthält. Er kehrt dann zurück, bis er dort ankommt. Ich vermute auch, dass dieser zweite Stapel nur bei einem Wurf und bei jedem Versuch / Fang berührt wird. AFAICT, das ein ähnliches Verhalten mit Rückkehrcodes implementiert, würde dieselbe Zeit in Anspruch nehmen. Aber das ist alles nur eine Vermutung, also möchte ich wissen, was wirklich passiert.
Wie funktionieren Ausnahmen wirklich?
Antworten:
Anstatt zu raten, habe ich mich entschlossen, den generierten Code mit einem kleinen Stück C ++ - Code und einer etwas alten Linux-Installation zu betrachten.
Ich habe es mit kompiliert
g++ -m32 -W -Wall -O3 -save-temps -c
und mir die generierte Assembly-Datei angesehen._ZN11MyExceptionD1Ev
istMyException::~MyException()
, so dass der Compiler entschieden uns, es nicht-Inline - Kopie des destructor benötigt.Überraschung! Auf dem normalen Codepfad gibt es überhaupt keine zusätzlichen Anweisungen. Der Compiler generierte stattdessen zusätzliche Offline-Fixup-Codeblöcke, auf die über eine Tabelle am Ende der Funktion verwiesen wird (die tatsächlich in einem separaten Abschnitt der ausführbaren Datei abgelegt ist). Alle Arbeiten werden hinter den Kulissen von der Standardbibliothek ausgeführt, die auf diesen Tabellen (
_ZTI11MyException
istypeinfo for MyException
) basiert .OK, das war eigentlich keine Überraschung für mich, ich wusste bereits, wie dieser Compiler das gemacht hat. Fortsetzung der Baugruppenausgabe:
Hier sehen wir den Code zum Auslösen einer Ausnahme. Während es keinen zusätzlichen Overhead gab, nur weil eine Ausnahme ausgelöst werden könnte, ist es offensichtlich viel Overhead, eine Ausnahme tatsächlich zu werfen und abzufangen. Das meiste davon ist darin verborgen
__cxa_throw
, was sein muss:Vergleichen Sie dies mit den Kosten für die einfache Rückgabe eines Werts, und Sie sehen, warum Ausnahmen nur für außergewöhnliche Rückgaben verwendet werden sollten.
Zum Abschluss der restlichen Assembly-Datei:
Die typeinfo Daten.
Noch mehr Ausnahmebehandlungstabellen und verschiedene zusätzliche Informationen.
Die Schlussfolgerung, zumindest für GCC unter Linux: Die Kosten sind zusätzlicher Speicherplatz (für die Handler und Tabellen), unabhängig davon, ob Ausnahmen ausgelöst werden, sowie die zusätzlichen Kosten für das Parsen der Tabellen und das Ausführen der Handler, wenn eine Ausnahme ausgelöst wird. Wenn Sie Ausnahmen anstelle von Fehlercodes verwenden und ein Fehler selten ist, kann er schneller sein , da Sie nicht mehr den Aufwand haben, auf Fehler zu testen.
Wenn Sie weitere Informationen wünschen, insbesondere zu allen
__cxa_
Funktionen, lesen Sie die ursprüngliche Spezifikation, aus der sie stammen:quelle
Ausnahmen, die langsam waren, waren früher wahr.
In den meisten modernen Compilern gilt dies nicht mehr.
Hinweis: Nur weil wir Ausnahmen haben, heißt das nicht, dass wir auch keine Fehlercodes verwenden. Wenn Fehler lokal behandelt werden können, verwenden Sie Fehlercodes. Wenn Fehler mehr Kontext für die Korrektur erfordern, verwenden Sie Ausnahmen: Ich habe es hier viel beredter geschrieben: Welche Grundsätze leiten Ihre Richtlinie zur Ausnahmebehandlung?
Die Kosten für den Code zur Ausnahmebehandlung, wenn keine Ausnahmen verwendet werden, sind praktisch Null.
Wenn eine Ausnahme ausgelöst wird, werden einige Arbeiten ausgeführt.
Sie müssen dies jedoch mit den Kosten für die Rückgabe von Fehlercodes vergleichen und diese bis zu dem Punkt überprüfen, an dem der Fehler behandelt werden kann. Beides ist zeitaufwändiger zu schreiben und zu warten.
Es gibt auch ein Problem für Anfänger:
Obwohl Ausnahmeobjekte klein sein sollen, stecken einige Leute viel Zeug in sie. Dann haben Sie die Kosten für das Kopieren des Ausnahmeobjekts. Die Lösung dort ist zweifach:
Meiner Meinung nach würde ich wetten, dass derselbe Code mit Ausnahmen entweder effizienter oder mindestens so vergleichbar ist wie der Code ohne Ausnahmen (verfügt jedoch über den gesamten zusätzlichen Code zur Überprüfung der Funktionsfehlerergebnisse). Denken Sie daran, dass Sie nichts umsonst bekommen. Der Compiler generiert den Code, den Sie ursprünglich geschrieben haben sollten, um Fehlercodes zu überprüfen (und normalerweise ist der Compiler viel effizienter als ein Mensch).
quelle
Es gibt eine Reihe von Möglichkeiten, wie Sie Ausnahmen implementieren können. In der Regel hängen diese jedoch von der Unterstützung des Betriebssystems ab. Unter Windows ist dies der strukturierte Mechanismus zur Behandlung von Ausnahmen.
Die Details zu Code Project werden ausführlich diskutiert: Wie ein C ++ - Compiler die Ausnahmebehandlung implementiert
Der Overhead von Ausnahmen tritt auf, weil der Compiler Code generieren muss, um zu verfolgen, welche Objekte in jedem Stapelrahmen (oder genauer gesagt im Bereich) zerstört werden müssen, wenn sich eine Ausnahme aus diesem Bereich ausbreitet. Wenn eine Funktion keine lokalen Variablen auf dem Stapel hat, für die Destruktoren aufgerufen werden müssen, sollte sie keine Leistungseinbußen bei der Ausnahmebehandlung aufweisen.
Die Verwendung eines Rückkehrcodes kann jeweils nur eine Ebene des Stapels abwickeln, während ein Ausnahmebehandlungsmechanismus in einem Vorgang viel weiter nach unten springen kann, wenn in den Zwischenstapelrahmen nichts zu tun ist.
quelle
Matt Pietrek hat einen ausgezeichneten Artikel über Win32 Structured Exception Handling geschrieben . Während dieser Artikel ursprünglich 1997 geschrieben wurde, gilt er noch heute (aber natürlich nur für Windows).
quelle
Dieser Artikel untersucht das Problem und stellt im Grunde fest, dass Ausnahmen in der Praxis Laufzeitkosten verursachen, obwohl die Kosten relativ niedrig sind, wenn die Ausnahme nicht ausgelöst wird. Guter Artikel, empfohlen.
quelle
Ein Freund von mir hat vor einigen Jahren geschrieben, wie Visual C ++ mit Ausnahmen umgeht.
http://www.xyzw.de/c160.html
quelle
Alles gute Antworten.
Denken Sie auch darüber nach, wie viel einfacher es ist, Code zu debuggen, der "if-Checks" als Gates am Anfang von Methoden ausführt, anstatt dem Code zu erlauben, Ausnahmen auszulösen.
Mein Motto ist, dass es einfach ist, Code zu schreiben, der funktioniert. Das Wichtigste ist, den Code für die nächste Person zu schreiben, die ihn sich ansieht. In einigen Fällen bist du es in 9 Monaten und du willst deinen Namen nicht verfluchen!
quelle