Warum dieser Leistungsunterschied? (Ausnahme fangen)

7

Nachdem ich hier eine Frage darüber gelesen hatte, welche Dinge unser Computer in einer Sekunde erledigen könnte, machte ich einen kleinen Test, den ich für eine Weile im Hinterkopf hatte, und ich bin sehr überrascht von den Ergebnissen. Schau:

Einfaches Programm, um eine Null-Ausnahme zu erfassen, dauert fast eine Sekunde, um 1900 Iterationen durchzuführen:

%Vor%

Alternativ kann vor der Zuweisung überprüft werden, ob test == null ist, das gleiche Programm kann 200000000 Iterationen in einer Sekunde ausführen.

%Vor%

Jeder hat eine detaillierte Erklärung, warum dieser RIESIGE Unterschied?

BEARBEITEN: Wenn ich den Test im Freigabemodus außerhalb von Visual Studio durchführe, erhalte ich 35000-40000 Iterationen gegenüber 400000000 Iterationen (immer ungefähr)

Hinweis Ich führe das mit einem beschissenen PIV 3.06Ghz

    
Drevak 07.12.2009, 14:05
quelle

7 Antworten

12

Es gibt keine Möglichkeit, die für 1900 Iterationen eine Sekunde dauern sollte, es sei denn, Sie laufen im Debugger. Das Ausführen von Leistungstests unter dem Debugger ist eine schlechte Idee.

BEARBEITEN: Beachten Sie, dass dies nicht der Fall ist, wenn Sie zum Release build wechseln - es ist ein Fall, ohne den Debugger zu laufen; d. h. Drücken von Strg-F5 anstelle von F5 .

Nachdem das gesagt wurde, ist es auch eine schlechte Idee, Ausnahmen zu provozieren, wenn man sie sehr leicht vermeiden kann.

Ich gehe von der Durchführung von Ausnahmen aus: Wenn Sie sie ordnungsgemäß verwenden, sollten sie keine wesentlichen Leistungsprobleme verursachen, außer Sie befinden sich in einer katastrophalen Situation (z. B. wenn Sie es versuchen) machen Hunderttausende von Web-Service-Anrufen und das Netzwerk ist ausgefallen).

Ausnahmen sind teuer unter Debuggern - jedenfalls in Visual Studio sowieso - wegen der Ausarbeitung, ob man in den Debugger usw. einbricht oder nicht, und wahrscheinlich jede Menge Stapelanalyse, die sonst unnötig ist. Sie sind immer noch etwas teuer, aber Sie sollten nicht genug von ihnen werfen, um es zu bemerken. Es gibt immernoch einen Stack-Abbau, relevante Catch-Handler zu finden, usw. - aber das sollte nur passieren, wenn etwas nicht stimmt.

BEARBEITEN: Sicher, das Auslösen einer Ausnahme wird Ihnen immer weniger Iterationen pro Sekunde geben (obwohl 35000 immer noch eine sehr niedrige Zahl ist - ich würde über 100 K erwarten), weil Sie fast nichts im Nicht-Ausnahme-Fall. Schauen wir uns die beiden an:

Nicht-Ausnahmeversion des Schleifenkörpers

  • Weisen Sie der Variablen
  • null zu
  • Überprüfen Sie, ob die Variable null ist; es ist, also gehe zurück zum Anfang der Schleife

(Wie in den Kommentaren erwähnt, ist es durchaus möglich, dass das JIT das sowieso weiter optimiert ...)

Ausnahmeversion:

  • Weisen Sie der Variablen
  • null zu
  • Dereferenzierungsvariable
    • Implizite Überprüfung auf Nichtigkeit
    • Erstellen Sie ein Ausnahmeobjekt
    • Überprüfen Sie, ob gefilterte Ausnahmehandler
    • aufrufen
    • Suchen Sie im Stapel nach dem Fangblock, um zu
    • zu springen
    • Überprüfen Sie, ob alle Blöcke blockiert sind
    • Verzweigen Sie sich entsprechend

Ist es ein Wunder, dass Sie weniger Leistung sehen?

Vergleichen Sie das nun mit der häufigeren Situation, in der Sie eine ganze Menge Arbeit erledigen, möglicherweise IO, Objekterstellung usw. - und vielleicht eine Ausnahme ausgelöst wird. Dann wird der Unterschied viel weniger signifikant.

    
Jon Skeet 07.12.2009, 14:09
quelle
2

Sehen Sie sich Chris Brummes Blog mit besonderem Augenmerk auf an Performance und Trends Abschnitt für eine Erklärung, warum Ausnahmen langsam sind. Sie werden aus einem Grund als "Ausnahmen" bezeichnet: Sie sollten nicht sehr oft vorkommen.

    
Gonzalo 07.12.2009 14:10
quelle
2

Sie können diese beliebte Frage auch hilfreich finden: Wie langsam sind .NET-Ausnahmen?

    
DOK 07.12.2009 14:12
quelle
2

Hier ist ein weiterer Faktor. Wenn die PDB-Datei im ausführenden Verzeichnis vorhanden ist, liest die .NET-Laufzeitumgebung beim Auslösen der Ausnahme die PDB-Datei, um die Codezeilennummer in die Ablaufverfolgung für den Ausnahme-Stack aufzunehmen. Das dauert ziemlich viel Zeit. Versuchen Sie Ihre erste Methode (die mit Ausnahme) mit und ohne die .pdb-Datei im ausführenden Verzeichnis.

Ich hatte einen einfachen Timing-Test mit und ohne die .pdb als Antwort auf eine andere Frage durchgeführt, hier .

    
CodingWithSpike 07.12.2009 14:31
quelle
1

Eine Optimierung, die der Compiler durchführt, und ich glaube, es könnte "tote Code-Eliminierung" sein; auch abhängig von dem Compiler, den Sie verwenden, tut letzteres Programm tatsächlich, was Assembler folk ein "no-op" nennt.

    
Raymond Tay 07.12.2009 14:21
quelle
1

In meinen Tests ist der "außergewöhnliche" Code nicht so langsam - viel langsamer, aber nicht so viel. Der Unterschied liegt darin, das Objekt "Exception" (oder genauer gesagt "NullReferenceException") zu erstellen. Der langsamste Teil ist das Abrufen der Zeichenfolge für die Ausnahmebedingungsnachricht - es gibt einen internen Aufruf von GetResourceString - und das Abrufen des Stack-Trace.

    
mYsZa 07.12.2009 14:26
quelle
0

Dies ist ein schrecklicher Mikro-Benchmark.

Die letztere "optimierte" Schleife hat als eine Kompilierzeit-Invariante, dass der Test immer Null ist, so dass es nicht nötig ist, das Kompilieren in der versuchten Zuweisung zu stören. Sie testen eine leere Schleife, indem Sie jedes Mal eine Ausnahme auslösen.

Ein wirklich guter jit könnte sogar die Schleife komplett entfernen, wobei er feststellt, dass die Schleife keinen Körper hat, also keine Nebenwirkungen, die den Zähler nicht erhöhen und dass der Zähler selbst ungenutzt ist (dies ist unwahrscheinlich, da eine solche Optimierung hätte wenig Dienstprogramm in der realen Welt).

Ausnahmeregelungen sind relativ teuer (in Bezug auf den herkömmlichen Verzweigungskontrollfluss) [1], hauptsächlich aufgrund von drei Dingen:

  1. Alle Ausnahmen sind Referenztypen und daher (vorerst) sind Heap allokiert und anschließend Garbage gesammelt.
  2. Die Stack-Ebenen, die in der Exception enthalten sind (Dies ist proportional zur Entfernung, die der Stack abwickelt - etwas, das Sie nicht vollständig messen können)
  3. Wenn wir in den Ausnahmebehandlungscode gehen, werden alle netten Dinge wie die Verzweigungsvorhersage übersprungen, die es dem Prozessor mit hoher Pipeline ermöglichen, sich selbst etwas Nützliches zu tun

Das Werfen und Abfangen von Ausnahmen innerhalb einer engen Schleife ist fast sicher ein massiv fehlerhaftes Design, aber wenn Sie diese Wirkung messen wollen, sollten Sie eine Schleife schreiben, die das tatsächlich tut.

  1. teuer ist hier ein sehr relativer Begriff. Sie können immer noch Zehntausende von ihnen pro Sekunde auf bescheidener Hardware machen.
ShuggyCoUk 07.12.2009 14:39
quelle