Ich möchte keine Diskussion darüber, wann Ausnahmen gemacht werden sollen und wann nicht. Ich möchte ein einfaches Problem lösen. In 99% der Fälle dreht sich das Argument, keine Ausnahmen zu werfen, darum, dass sie langsam sind, während die andere Seite (mit Benchmark-Test) behauptet, dass die Geschwindigkeit nicht das Problem ist. Ich habe zahlreiche Blogs, Artikel und Beiträge gelesen, die sich auf die eine oder andere Seite beziehen. Also was ist es?
c#
.net
performance
exception
Goran
quelle
quelle
Antworten:
Ich bin auf der "nicht langsamen" Seite - oder genauer gesagt "nicht langsam genug, damit es sich lohnt, sie bei normalem Gebrauch zu vermeiden". Ich habe zwei kurze Artikel darüber geschrieben. Es gibt Kritikpunkte am Benchmark-Aspekt, die hauptsächlich darauf zurückzuführen sind, dass "im wirklichen Leben mehr Stapel durchlaufen werden müssen, sodass Sie den Cache sprengen usw." - aber die Verwendung von Fehlercodes, um sich den Stapel hochzuarbeiten, würde dies auch tun den Cache sprengen, deshalb sehe ich das nicht als besonders gutes Argument.
Nur um es klar zu machen - ich unterstütze die Verwendung von Ausnahmen, bei denen sie nicht logisch sind, nicht. Zum Beispiel
int.TryParse
ist es völlig geeignet, Daten von einem Benutzer zu konvertieren. Es ist angemessen, wenn Sie eine maschinengenerierte Datei lesen, bei der ein Fehler bedeutet: "Die Datei hat nicht das Format, das sie haben soll. Ich möchte wirklich nicht versuchen, damit umzugehen, da ich nicht weiß, was sonst noch falsch sein könnte." ""Bei der Verwendung von Ausnahmen unter "nur vernünftigen Umständen" habe ich noch nie eine Anwendung gesehen, deren Leistung durch Ausnahmen erheblich beeinträchtigt wurde. Grundsätzlich sollten Ausnahmen nicht oft auftreten, es sei denn, Sie haben signifikante Korrektheitsprobleme, und wenn Sie signifikante Korrektheitsprobleme haben, ist die Leistung nicht das größte Problem, mit dem Sie konfrontiert sind.
quelle
Darauf gibt es die endgültige Antwort desjenigen, der sie implementiert hat - Chris Brumme. Er hat einen ausgezeichneten Blog-Artikel über das Thema geschrieben (Warnung - es ist sehr lang) (Warnung2 - es ist sehr gut geschrieben, wenn Sie ein Technikfreak sind, werden Sie es bis zum Ende lesen und müssen dann Ihre Stunden nach der Arbeit nachholen :) )
Die Zusammenfassung: Sie sind langsam. Sie sind als Win32-SEH-Ausnahmen implementiert, sodass einige sogar die Ring-0-CPU-Grenze überschreiten! In der realen Welt werden Sie natürlich eine Menge anderer Arbeiten ausführen, sodass die eine oder andere Ausnahme überhaupt nicht bemerkt wird. Wenn Sie sie jedoch für den Programmfluss verwenden, sollten Sie damit rechnen, dass Ihre App gehämmert wird. Dies ist ein weiteres Beispiel für die MS-Marketingmaschine, die uns einen schlechten Dienst leistet. Ich erinnere mich an eine Microsoft, die uns erzählte, wie sie absolut keinen Overhead verursacht haben, was völlig zu kurz kommt.
Chris gibt ein passendes Zitat:
quelle
Ich habe keine Ahnung, wovon die Leute sprechen, wenn sie sagen, dass sie nur langsam sind, wenn sie geworfen werden.
BEARBEITEN: Wenn keine Ausnahmen ausgelöst werden, bedeutet dies, dass Sie eine neue Ausnahme () oder ähnliches ausführen. Andernfalls führt die Ausnahme dazu, dass der Thread angehalten und der Stapel ausgeführt wird. Dies mag in kleineren Situationen in Ordnung sein, aber auf stark frequentierten Websites führt das Verlassen auf Ausnahmen als Workflow- oder Ausführungspfadmechanismus sicherlich zu Leistungsproblemen. Ausnahmen an sich sind nicht schlecht und nützlich, um außergewöhnliche Bedingungen auszudrücken
Der Ausnahme-Workflow in einer .NET-App verwendet Ausnahmen der ersten und zweiten Chance. Für alle Ausnahmen wird das Ausnahmeobjekt weiterhin erstellt, auch wenn Sie sie abfangen und behandeln, und das Framework muss den Stapel noch durchlaufen, um nach einem Handler zu suchen. Wenn Sie fangen und erneut werfen, wird das natürlich länger dauern - Sie werden eine Ausnahme der ersten Chance erhalten, sie fangen, erneut werfen und eine weitere Ausnahme der ersten Chance verursachen, die dann keinen Handler findet, der dann verursacht eine Ausnahme der zweiten Chance.
Ausnahmen sind auch Objekte auf dem Heap. Wenn Sie also Unmengen von Ausnahmen auslösen, verursachen Sie sowohl Leistungs- als auch Speicherprobleme.
Laut meiner vom ACE-Team verfassten Kopie von "Leistungstests von Microsoft .NET-Webanwendungen":
"Die Ausnahmebehandlung ist teuer. Die Ausführung des beteiligten Threads wird angehalten, während die CLR auf der Suche nach dem richtigen Ausnahmebehandler durch den Aufrufstapel rekursiert. Wenn sie gefunden wird, müssen der Ausnahmebehandler und einige Anzahl von Endblöcken alle die Möglichkeit haben, ausgeführt zu werden bevor die reguläre Verarbeitung durchgeführt werden kann. "
Meine eigenen Erfahrungen auf diesem Gebiet haben gezeigt, dass die Reduzierung von Ausnahmen die Leistung erheblich verbessert. Natürlich gibt es noch andere Dinge, die Sie beim Leistungstest berücksichtigen - wenn beispielsweise Ihre Festplatten-E / A-Vorgänge ausgeführt werden oder Ihre Abfragen in Sekundenschnelle erfolgen, sollte dies Ihr Fokus sein. Das Finden und Entfernen von Ausnahmen sollte jedoch ein wesentlicher Bestandteil dieser Strategie sein.
quelle
Das Argument, wie ich es verstehe, ist nicht, dass das Auslösen von Ausnahmen schlecht ist, sie sind per se langsam. Stattdessen geht es darum, das throw / catch-Konstrukt als erstklassige Methode zur Steuerung der normalen Anwendungslogik zu verwenden, anstatt herkömmlicherer bedingter Konstrukte.
In der normalen Anwendungslogik führen Sie häufig Schleifen durch, bei denen dieselbe Aktion tausende / millionenfach wiederholt wird. In diesem Fall können Sie mit einigen sehr einfachen Profilen (siehe Stoppuhr-Klasse) selbst sehen, dass das Auslösen einer Ausnahme anstelle einer einfachen if-Anweisung wesentlich langsamer ausfallen kann.
Tatsächlich habe ich einmal gelesen, dass das .NET-Team von Microsoft die TryXXXXX-Methoden in .NET 2.0 für viele der Basis-FCL-Typen eingeführt hat, insbesondere weil Kunden sich darüber beschwerten, dass die Leistung ihrer Anwendungen so langsam war.
In vielen Fällen stellte sich heraus, dass Kunden versuchten, Werte in einer Schleife vom Typ zu konvertieren, und jeder Versuch fehlschlug. Eine Konvertierungsausnahme wurde ausgelöst und dann von einem Ausnahmebehandler abgefangen, der die Ausnahme verschluckte und die Schleife fortsetzte.
Microsoft empfiehlt jetzt, die TryXXX-Methoden besonders in dieser Situation zu verwenden, um solche möglichen Leistungsprobleme zu vermeiden.
Ich könnte mich irren, aber es scheint, als ob Sie sich nicht sicher sind, ob die "Benchmarks", über die Sie gelesen haben, wahr sind. Einfache Lösung: Probieren Sie es selbst aus.
quelle
Mein XMPP-Server hat einen erheblichen Geschwindigkeitsschub erhalten (sorry, keine tatsächlichen Zahlen, rein beobachtend), nachdem ich konsequent versucht habe, sie zu verhindern (z. B. zu überprüfen, ob ein Socket angeschlossen ist, bevor ich versuche, mehr Daten zu lesen) und mir Möglichkeiten gegeben habe, sie zu vermeiden (die genannten TryX-Methoden). Das war mit nur etwa 50 aktiven (Chat-) virtuellen Benutzern.
quelle
Nur um meine eigenen jüngsten Erfahrungen zu dieser Diskussion hinzuzufügen: In Übereinstimmung mit den meisten der oben beschriebenen Punkte stellte ich fest, dass das Auslösen von Ausnahmen bei wiederholter Ausführung extrem langsam ist, auch ohne dass der Debugger ausgeführt wird. Ich habe gerade die Leistung eines großen Programms, das ich schreibe, um 60% gesteigert, indem ich ungefähr fünf Codezeilen geändert habe: Wechseln zu einem Rückkehrcode-Modell, anstatt Ausnahmen auszulösen. Zugegeben, der fragliche Code wurde tausende Male ausgeführt und hat möglicherweise Tausende von Ausnahmen ausgelöst, bevor ich ihn geändert habe. Daher stimme ich der obigen Aussage zu: Wirf Ausnahmen aus, wenn etwas Wichtiges tatsächlich schief geht, und nicht, um den Anwendungsfluss in "erwarteten" Situationen zu steuern.
quelle
Wenn Sie sie mit Rückkehrcodes vergleichen, sind sie höllisch langsam. Wie bereits in früheren Postern erwähnt, möchten Sie den normalen Programmbetrieb nicht aktivieren, sodass Sie nur dann den perfekten Treffer erzielen, wenn ein Problem auftritt und in den allermeisten Fällen die Leistung keine Rolle mehr spielt (da die Ausnahme ohnehin eine Straßensperre impliziert).
Es lohnt sich auf jeden Fall, sie gegenüber Fehlercodes zu verwenden. Die Vorteile sind enorme IMO.
quelle
Ich hatte noch nie Leistungsprobleme mit Ausnahmen. Ich verwende häufig Ausnahmen - ich verwende niemals Rückkehrcodes, wenn ich kann. Sie sind eine schlechte Praxis und riechen meiner Meinung nach nach Spaghetti-Code.
Ich denke, es läuft alles darauf hinaus, wie Sie Ausnahmen verwenden: Wenn Sie sie wie Rückkehrcodes verwenden (jeder Methodenaufruf im Stapel fängt und wirft erneut), dann sind sie langsam, weil Sie jeden einzelnen Fang / Wurf über Kopf haben.
Wenn Sie jedoch am unteren Rand des Stapels werfen und am oberen Rand fangen (Sie ersetzen eine ganze Kette von Rückkehrcodes durch einen Wurf / Fang), werden alle kostspieligen Vorgänge einmal ausgeführt.
Letztendlich sind sie eine gültige Sprachfunktion.
Nur um meinen Standpunkt zu beweisen
Bitte führen Sie den Code unter diesem Link aus (zu groß für eine Antwort).
Ergebnisse auf meinem Computer:
marco@sklivvz:~/develop/test$ mono Exceptions.exe | grep PM
10/2/2008 2:53:32 PM
10/2/2008 2:53:42 PM
10/2/2008 2:53:52 PM
Zeitstempel werden am Anfang zwischen Rückkehrcodes und Ausnahmen am Ende ausgegeben. In beiden Fällen dauert es gleich lange. Beachten Sie, dass Sie mit Optimierungen kompilieren müssen.
quelle
Aber Mono löst eine Ausnahme 10x schneller als der .net-Standalone-Modus aus, und der .net-Standalone-Modus löst eine Ausnahme 60x schneller als der .net-Debugger-Modus aus. (Testmaschinen haben das gleiche CPU-Modell)
quelle
Im Release-Modus ist der Overhead minimal.
Ich bezweifle, dass Sie den Unterschied bemerken werden, es sei denn, Sie werden Ausnahmen für die Flusskontrolle (z. B. nicht lokale Exits) rekursiv verwenden.
quelle
In der Windows-CLR ist das Auslösen einer Ausnahme für eine Aufrufkette mit einer Tiefe von 8 750-mal langsamer als das Überprüfen und Weitergeben eines Rückgabewerts. (siehe unten für Benchmarks)
Diese hohen Kosten für Ausnahmen sind darauf zurückzuführen, dass die Windows-CLR in die sogenannte strukturierte Ausnahmebehandlung von Windows integriert ist . Auf diese Weise können Ausnahmen ordnungsgemäß abgefangen und über verschiedene Laufzeiten und Sprachen hinweg ausgelöst werden. Es ist jedoch sehr sehr langsam.
Ausnahmen in der Mono-Laufzeit (auf jeder Plattform) sind viel schneller, da sie nicht in SEH integriert sind. Es gibt jedoch einen Funktionsverlust, wenn Ausnahmen über mehrere Laufzeiten hinweg übergeben werden, da nichts wie SEH verwendet wird.
Hier sind die abgekürzten Ergebnisse meines Benchmarks für Ausnahmen und Rückgabewerte für die Windows-CLR.
Und hier ist der Code ..
quelle
Ein kurzer Hinweis hier zur Leistung beim Abfangen von Ausnahmen.
Wenn der Ausführungspfad in einen 'Try'-Block eintritt, passiert nichts Magisches. Es gibt keine 'try'-Anweisung und keine Kosten, die mit dem Betreten oder Verlassen des try-Blocks verbunden sind. Informationen zum try-Block werden in den Metadaten der Methode gespeichert, und diese Metadaten werden zur Laufzeit verwendet, wenn eine Ausnahme ausgelöst wird. Die Ausführungs-Engine geht den Stapel entlang und sucht nach dem ersten Aufruf, der in einem try-Block enthalten war. Der mit der Ausnahmebehandlung verbundene Overhead tritt nur auf, wenn Ausnahmen ausgelöst werden.
quelle
Beim Schreiben von Klassen / Funktionen für andere scheint es schwierig zu sein, zu sagen, wann Ausnahmen angemessen sind. Es gibt einige nützliche Teile von BCL, die ich loswerden und Pinvoke machen musste, weil sie Ausnahmen auslösen, anstatt Fehler zurückzugeben. In einigen Fällen können Sie es umgehen, in anderen Fällen wie System.Management und Leistungsindikatoren gibt es Verwendungen, bei denen Sie Schleifen ausführen müssen, in denen Ausnahmen häufig von BCL ausgelöst werden.
Wenn Sie eine Bibliothek schreiben und die Möglichkeit besteht, dass Ihre Funktion in einer Schleife verwendet wird und eine große Anzahl von Iterationen möglich ist, verwenden Sie das Try .. -Muster oder eine andere Methode, um die Fehler neben Ausnahmen anzuzeigen. Und selbst dann ist es schwer zu sagen, wie oft Ihre Funktion aufgerufen wird, wenn sie von vielen Prozessen in einer gemeinsam genutzten Umgebung verwendet wird.
In meinem eigenen Code werden Ausnahmen nur ausgelöst, wenn die Dinge so außergewöhnlich sind, dass es notwendig ist, in der Stapelverfolgung nachzusehen, was schief gelaufen ist, und sie dann zu beheben. Daher habe ich Teile von BCL so gut wie neu geschrieben, um die Fehlerbehandlung basierend auf dem Try .. -Muster anstelle von Ausnahmen zu verwenden.
quelle