Frage

Ich sehe ständig Leute sagen, dass Ausnahmen nur langsam, aber ich sehe nie einen Beweis. Anstatt also zu fragen, ob sie sind, werde ich fragen, wie Sie Ausnahmen hinter den Kulissen arbeiten, so kann ich ein Entscheidung treffen, wenn sie zu benutzen, und wenn sie langsam.

Von dem, was ich weiß, Ausnahmen sind das gleiche wie ein Bündel Rückkehr zu tun, aber es überprüft auch, wenn es die Rückkehr zu stoppen braucht tun. Wie überprüft es, wenn aufhören zu tun? Ich nehme eine Vermutung und sagen es einen zweiten Stapel ist, die die Art von Ausnahme und Stack-Position hält dann tut kehrt, bis er dort ankommt. Ich vermute auch das einzig Mal, dass Stack ist Berührung auf einem Wurf ist und jeder try / catch. AFAICT Umsetzung ein ähnliches Verhalten mit dem Rückkehrcode würde die gleiche Menge an Zeit in Anspruch nehmen. Aber das ist alles eine Vermutung, so will ich wissen.

Wie wirklich Ausnahmen arbeiten?

War es hilfreich?

Lösung

Anstatt zu raten, entschied ich mich tatsächlich aussehen zu auf dem generierten Code mit einem kleinen Stück von C ++ Code und eine etwas alten Linux installieren.

class MyException
{
public:
    MyException() { }
    ~MyException() { }
};

void my_throwing_function(bool throwit)
{
    if (throwit)
        throw MyException();
}

void another_function();
void log(unsigned count);

void my_catching_function()
{
    log(0);
    try
    {
        log(1);
        another_function();
        log(2);
    }
    catch (const MyException& e)
    {
        log(3);
    }
    log(4);
}

ich es mit g++ -m32 -W -Wall -O3 -save-temps -c zusammengestellt, und schaute auf die generierte Assembly-Datei.

    .file   "foo.cpp"
    .section    .text._ZN11MyExceptionD1Ev,"axG",@progbits,_ZN11MyExceptionD1Ev,comdat
    .align 2
    .p2align 4,,15
    .weak   _ZN11MyExceptionD1Ev
    .type   _ZN11MyExceptionD1Ev, @function
_ZN11MyExceptionD1Ev:
.LFB7:
    pushl   %ebp
.LCFI0:
    movl    %esp, %ebp
.LCFI1:
    popl    %ebp
    ret
.LFE7:
    .size   _ZN11MyExceptionD1Ev, .-_ZN11MyExceptionD1Ev

_ZN11MyExceptionD1Ev ist MyException::~MyException(), so dass der Compiler entschieden uns, es nicht-Inline-Kopie des destructor benötigt.

.globl __gxx_personality_v0
.globl _Unwind_Resume
    .text
    .align 2
    .p2align 4,,15
.globl _Z20my_catching_functionv
    .type   _Z20my_catching_functionv, @function
_Z20my_catching_functionv:
.LFB9:
    pushl   %ebp
.LCFI2:
    movl    %esp, %ebp
.LCFI3:
    pushl   %ebx
.LCFI4:
    subl    $20, %esp
.LCFI5:
    movl    $0, (%esp)
.LEHB0:
    call    _Z3logj
.LEHE0:
    movl    $1, (%esp)
.LEHB1:
    call    _Z3logj
    call    _Z16another_functionv
    movl    $2, (%esp)
    call    _Z3logj
.LEHE1:
.L5:
    movl    $4, (%esp)
.LEHB2:
    call    _Z3logj
    addl    $20, %esp
    popl    %ebx
    popl    %ebp
    ret
.L12:
    subl    $1, %edx
    movl    %eax, %ebx
    je  .L16
.L14:
    movl    %ebx, (%esp)
    call    _Unwind_Resume
.LEHE2:
.L16:
.L6:
    movl    %eax, (%esp)
    call    __cxa_begin_catch
    movl    $3, (%esp)
.LEHB3:
    call    _Z3logj
.LEHE3:
    call    __cxa_end_catch
    .p2align 4,,3
    jmp .L5
.L11:
.L8:
    movl    %eax, %ebx
    .p2align 4,,6
    call    __cxa_end_catch
    .p2align 4,,6
    jmp .L14
.LFE9:
    .size   _Z20my_catching_functionv, .-_Z20my_catching_functionv
    .section    .gcc_except_table,"a",@progbits
    .align 4
.LLSDA9:
    .byte   0xff
    .byte   0x0
    .uleb128 .LLSDATT9-.LLSDATTD9
.LLSDATTD9:
    .byte   0x1
    .uleb128 .LLSDACSE9-.LLSDACSB9
.LLSDACSB9:
    .uleb128 .LEHB0-.LFB9
    .uleb128 .LEHE0-.LEHB0
    .uleb128 0x0
    .uleb128 0x0
    .uleb128 .LEHB1-.LFB9
    .uleb128 .LEHE1-.LEHB1
    .uleb128 .L12-.LFB9
    .uleb128 0x1
    .uleb128 .LEHB2-.LFB9
    .uleb128 .LEHE2-.LEHB2
    .uleb128 0x0
    .uleb128 0x0
    .uleb128 .LEHB3-.LFB9
    .uleb128 .LEHE3-.LEHB3
    .uleb128 .L11-.LFB9
    .uleb128 0x0
.LLSDACSE9:
    .byte   0x1
    .byte   0x0
    .align 4
    .long   _ZTI11MyException
.LLSDATT9:

Überraschung! Es fallen keine zusätzlichen Anweisungen an alle auf dem normalen Codepfad. Der Compiler erzeugte stattdessen zusätzliche out-of-line-Blöcke Fixup-Code über eine Tabelle am Ende der Funktion Bezug genommen (die auf einem separaten Abschnitt der ausführbaren setzen tatsächlich). Die ganze Arbeit hinter den Kulissen durch die Standard-Bibliothek durchgeführt, basierend auf diesen Tabellen (_ZTI11MyException ist typeinfo for MyException).

OK, das ist nicht wirklich eine Überraschung für mich war, wusste ich schon, wie diese Compiler es taten. Weiter mit dem Montage Ausgabe:

    .text
    .align 2
    .p2align 4,,15
.globl _Z20my_throwing_functionb
    .type   _Z20my_throwing_functionb, @function
_Z20my_throwing_functionb:
.LFB8:
    pushl   %ebp
.LCFI6:
    movl    %esp, %ebp
.LCFI7:
    subl    $24, %esp
.LCFI8:
    cmpb    $0, 8(%ebp)
    jne .L21
    leave
    ret
.L21:
    movl    $1, (%esp)
    call    __cxa_allocate_exception
    movl    $_ZN11MyExceptionD1Ev, 8(%esp)
    movl    $_ZTI11MyException, 4(%esp)
    movl    %eax, (%esp)
    call    __cxa_throw
.LFE8:
    .size   _Z20my_throwing_functionb, .-_Z20my_throwing_functionb

Hier sehen wir den Code eine Ausnahme zu werfen. Zwar gibt es keinen zusätzlichen Aufwand einfach war, weil eine Ausnahme könnte es geworfen wird, ist offensichtlich eine Menge Aufwand in tatsächlich zu werfen und eine Ausnahme zu kontrollieren. Das meiste davon ist in __cxa_throw versteckt, die müssen:

  • Gehen Sie den Stapel mit Hilfe der Ausnahmetabellen, bis es einen Handler für diese Ausnahme findet.
  • Entspannen Sie den Stapel, bis es zu diesem Handler wird.
  • Eigentlich den Handler aufrufen.

Vergleichen Sie das mit den Kosten nur einen Wert zurückgibt, und Sie sehen, warum Ausnahmen sollten nur für außergewöhnliche Erträge verwendet werden.

Zum Abschluss der Rest der Baugruppendatei:

    .weak   _ZTI11MyException
    .section    .rodata._ZTI11MyException,"aG",@progbits,_ZTI11MyException,comdat
    .align 4
    .type   _ZTI11MyException, @object
    .size   _ZTI11MyException, 8
_ZTI11MyException:
    .long   _ZTVN10__cxxabiv117__class_type_infoE+8
    .long   _ZTS11MyException
    .weak   _ZTS11MyException
    .section    .rodata._ZTS11MyException,"aG",@progbits,_ZTS11MyException,comdat
    .type   _ZTS11MyException, @object
    .size   _ZTS11MyException, 14
_ZTS11MyException:
    .string "11MyException"

Die Typeinfo-Daten.

    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zPL"
    .uleb128 0x1
    .sleb128 -4
    .byte   0x8
    .uleb128 0x6
    .byte   0x0
    .long   __gxx_personality_v0
    .byte   0x0
    .byte   0xc
    .uleb128 0x4
    .uleb128 0x4
    .byte   0x88
    .uleb128 0x1
    .align 4
.LECIE1:
.LSFDE3:
    .long   .LEFDE3-.LASFDE3
.LASFDE3:
    .long   .LASFDE3-.Lframe1
    .long   .LFB9
    .long   .LFE9-.LFB9
    .uleb128 0x4
    .long   .LLSDA9
    .byte   0x4
    .long   .LCFI2-.LFB9
    .byte   0xe
    .uleb128 0x8
    .byte   0x85
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI3-.LCFI2
    .byte   0xd
    .uleb128 0x5
    .byte   0x4
    .long   .LCFI5-.LCFI3
    .byte   0x83
    .uleb128 0x3
    .align 4
.LEFDE3:
.LSFDE5:
    .long   .LEFDE5-.LASFDE5
.LASFDE5:
    .long   .LASFDE5-.Lframe1
    .long   .LFB8
    .long   .LFE8-.LFB8
    .uleb128 0x4
    .long   0x0
    .byte   0x4
    .long   .LCFI6-.LFB8
    .byte   0xe
    .uleb128 0x8
    .byte   0x85
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI7-.LCFI6
    .byte   0xd
    .uleb128 0x5
    .align 4
.LEFDE5:
    .ident  "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)"
    .section    .note.GNU-stack,"",@progbits

Noch mehr Ausnahmebehandlung Tabellen und verschiedene zusätzliche Informationen.

So die Schlussfolgerung, zumindest für GCC auf Linux: die Kosten sind extra Platz (für die Handler und Tabellen), ob oder nicht Ausnahmen ausgelöst werden, sowie die zusätzlichen Kosten für die Tabellen-Parsing und die Handler ausgeführt wird, wenn eine Ausnahme ist geworfen. Wenn Sie Ausnahmen anstelle von Fehlercodes zu verwenden, und ein Fehler ist selten, kann es sein, schneller , da Sie nicht den Aufwand für das Testen mehr für Fehler haben.

Falls Sie weitere Informationen wünschen, insbesondere, was die alle __cxa_ Funktionen haben, finden Sie in der ursprünglichen Spezifikation sie kamen:

Andere Tipps

Ausnahmen sind langsam wurde true in den alten Tagen.
In den meisten modernen Compiler hält dies nicht mehr wahr.

Hinweis: Nur weil wir Ausnahmen haben, bedeutet nicht, dass wir Fehlercodes nicht so gut verwenden. Wenn Fehler gehandhabt werden kann lokal verwenden Fehlercodes. Wenn Fehler mehr Kontext für die Korrektur Ausnahmen Verwendung erfordern: Ich schrieb es viel mehr beredt hier: Was sind die Prinzipien Ihre Ausnahmebehandlung Politik führen?

Die Kosten für die Ausnahmebehandlung Code, wenn keine Ausnahmen verwendet werden, ist praktisch gleich Null.

Wenn eine Ausnahme ausgelöst wird, gibt es eine Arbeit erledigt.
Aber Sie haben dies zum Vergleich mit den Kosten der Fehlercodes zurückkehrt und überprüft sie den ganzen Weg zurück bis zum Punkt, wo der Fehler gehandhabt werden kann. Sowohl mehr Zeit zu schreiben und zu pflegen raubend.

Auch gibt es ein gotcha für Anfänger:
Obwohl Exception-Objekte sollen einige Leute zu klein setzen viele Sachen in ihnen. Dann haben Sie die Kosten für das Kopieren des Ausnahmeobjekts. Die Lösung gibt es zwei Ziele verfolgt:

  • Stellen Sie kein Extramaterial in Ihrer Ausnahme.
  • Fang durch konstante Referenz.

Meiner Meinung nach würde ich darauf wetten, dass der gleiche Code mit Ausnahmen entweder effizienter oder zumindest so vergleichbar wie der Code ohne die Ausnahmen (aber hat alle zusätzlichen Code-Funktion Fehler Ergebnisse zu überprüfen). Denken Sie daran, Sie sind nicht kostenlos Compiler immer etwas, um den Code zu erzeugen Sie an erster Stelle geschrieben haben, sollten Fehlercodes (und in der Regel der Compiler sind viel effizienter als ein Mensch).

prüfen

Es gibt eine Reihe von Möglichkeiten, wie Sie Ausnahmen implementieren könnte, aber in der Regel werden sie auf eine zugrunde liegende Unterstützung der OS verlassen. Unter Windows ist dies die strukturierte Ausnahmebehandlung.

Es gibt anständige Diskussion der Details auf Code Project: Wie ein C ++ Compiler implementiert Ausnahmebehandlung

Der Overhead von Ausnahmen tritt auf, weil die Compiler Code zu erzeugen hat, um zu verfolgen, welche Objekte in jedem Stapelrahmen zerstört werden muß (oder mehr Umfang genau), wenn eine Ausnahme von diesem Umfang ausbreitet. Wenn eine Funktion keine lokalen Variablen auf dem Stapel hat, die Destruktoren benötigen aufgerufen werden, dann sollte es keine Leistungseinbuße WRT Ausnahmebehandlung hat.

einen Return-Code verwendet, kann nur eine einzige Ebene des Stapels zu einer Zeit entspannen, während eine Ausnahmebehandlung viel weiter zurück in dem Stapel in einem Arbeitsgang springen kann, wenn es nichts für sie in dem Zwischenstapelrahmen zu tun.

Matt Pietrek schrieb einen ausgezeichneten Artikel über Win32 Structured Exception Handling . Während dieser Artikel ursprünglich im Jahr 1997 geschrieben wurde, ist es noch heute gilt (aber natürlich gilt nur für Windows).

dieser Artikel das Problem untersucht und findet im Grunde, dass in der Praxis ein Lauf- ist Zeit Kosten zu Ausnahmen, obwohl die Kosten relativ niedrig sind, wenn die Ausnahme nicht ausgelöst. Guter Artikel, empfohlen.

Ein Freund von mir hat ein bisschen wie Visual C ++ Exceptions vor einigen Jahren.

http://www.xyzw.de/c160.html

Alle guten Antworten.

Auch darüber nachdenken, wie viel einfacher es ist zu Debug-Code, der an der Spitze der Methoden ‚wenn Schecks‘, wie Gates tut stattdessen den Code erlauben Ausnahmen zu werfen.

Mein Motto ist, dass es einfach ist, Code zu schreiben, das funktioniert. Das Wichtigste ist, den Code für die nächste Person zu schreiben, die es betrachtet. In einigen Fällen ist es Ihnen in 9 Monaten, und Sie wollen nicht, Ihren Namen zu verfluchen!

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top