Antworten:
Ich bin kein Experte für Sprachimplementierungen (nehmen Sie dies also mit einem Körnchen Salz), aber ich denke, eine der größten Kosten besteht darin, den Stapel abzuwickeln und für die Stapelverfolgung zu speichern. Ich vermute, dass dies nur passiert, wenn die Ausnahme ausgelöst wird (aber ich weiß es nicht), und wenn ja, würde dies jedes Mal, wenn eine Ausnahme ausgelöst wird, eine angemessene Größe versteckter Kosten haben ... es ist also nicht so, als würden Sie nur von einem Ort springen Im Code zu einem anderen ist viel los.
Ich denke nicht, dass es ein Problem ist, solange Sie Ausnahmen für AUSSERGEWÖHNLICHES Verhalten verwenden (also nicht Ihren typischen, erwarteten Pfad durch das Programm).
Drei Punkte hier zu machen:
Erstens gibt es wenig oder KEINE Leistungseinbußen, wenn Sie tatsächlich Try-Catch-Blöcke in Ihrem Code haben. Dies sollte nicht berücksichtigt werden, wenn Sie vermeiden möchten, dass sie in Ihrer Anwendung enthalten sind. Der Leistungstreffer kommt nur ins Spiel, wenn eine Ausnahme ausgelöst wird.
Wenn zusätzlich zu den Stapelabwicklungsvorgängen usw., die von anderen erwähnt wurden, eine Ausnahme ausgelöst wird, sollten Sie sich darüber im Klaren sein, dass eine ganze Reihe von Laufzeit- / Reflexionsvorgängen ausgeführt wird, um die Mitglieder der Ausnahmeklasse wie die Stapelverfolgung zu füllen Objekt und die verschiedenen Typelemente usw.
Ich glaube, dass dies einer der Gründe ist, warum der allgemeine Ratschlag, wenn Sie throw;
die Ausnahme erneut auslösen möchten, darin besteht , die Ausnahme nicht erneut auszulösen oder eine neue zu erstellen, da in diesen Fällen alle diese Stapelinformationen wiedererlangt werden, während dies im einfachen Fall der Fall ist werfen es ist alles erhalten.
throw new Exception("Wrapping layer’s error", ex);
Fragen Sie nach dem Aufwand für die Verwendung von try / catch / finally, wenn keine Ausnahmen ausgelöst werden, oder nach dem Aufwand für die Verwendung von Ausnahmen zur Steuerung des Prozessflusses? Letzteres ähnelt in gewisser Weise der Verwendung eines Dynamitstabs zum Anzünden der Geburtstagskerze eines Kleinkindes, und der damit verbundene Aufwand fällt in die folgenden Bereiche:
Sie können zusätzliche Seitenfehler erwarten, da die Ausnahme auf nicht residenten Code und Daten zugreift, die normalerweise nicht im Arbeitssatz Ihrer Anwendung enthalten sind.
Beide oben genannten Elemente greifen normalerweise auf "kalten" Code und Daten zu, sodass harte Seitenfehler wahrscheinlich sind, wenn Sie überhaupt Speicherdruck haben:
Die tatsächlichen Auswirkungen der Kosten können sehr unterschiedlich sein, je nachdem, was zu diesem Zeitpunkt noch in Ihrem Code vor sich geht. Jon Skeet hat hier eine gute Zusammenfassung mit einigen nützlichen Links. Ich stimme seiner Aussage eher zu, dass Sie Probleme mit der Verwendung von Ausnahmen haben, die über die Leistung hinausgehen, wenn Sie an einem Punkt angelangt sind, an dem Ausnahmen Ihre Leistung erheblich beeinträchtigen.
Nach meiner Erfahrung besteht der größte Aufwand darin, eine Ausnahme auszulösen und damit umzugehen. Ich habe einmal an einem Projekt gearbeitet, bei dem Code ähnlich dem folgenden verwendet wurde, um zu überprüfen, ob jemand das Recht hatte, ein Objekt zu bearbeiten. Diese HasRight () -Methode wurde überall in der Präsentationsebene verwendet und wurde häufig für Hunderte von Objekten aufgerufen.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Wenn die Testdatenbank mit Testdaten voller wurde, führte dies zu einer sehr sichtbaren Verlangsamung beim Öffnen neuer Formulare usw.
Also habe ich es auf Folgendes umgestaltet, was - nach späteren schnellen und schmutzigen Messungen - etwa 2 Größenordnungen schneller ist:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Kurz gesagt, die Verwendung von Ausnahmen im normalen Prozessfluss ist etwa zwei Größenordnungen langsamer als die Verwendung eines ähnlichen Prozessflusses ohne Ausnahmen.
Im Gegensatz zu allgemein akzeptierten Theorien kann try
/ catch
erhebliche Auswirkungen auf die Leistung haben, und das ist, ob eine Ausnahme ausgelöst wird oder nicht!
Ersteres wurde im Laufe der Jahre in einigen Blog-Posts von Microsoft MVPs behandelt, und ich vertraue darauf, dass Sie sie leicht finden konnten, doch StackOverflow kümmert sich so sehr um den Inhalt, dass ich Links zu einigen von ihnen als Füllnachweis bereitstellen werde :
try
/ catch
/finally
( und Teil zwei ), von Peter Ritchie untersucht die Optimierungendietry
/catch
/finally
deaktiviert (und ich gehe weiter in diese mit Zitaten aus der Norm)Parse
vs. TryParse
vs.ConvertTo
von Ian Huff besagt offensichtlichdass „Ausnahmebehandlungsehr langsam“ und zeigt diesen Punkt durch LochfraßInt.Parse
undInt.TryParse
gegeneinander ... Für jeden,darauf bestehtdassTryParse
Anwendungentry
/catch
hinter den Kulissen, das sollte etwas Licht vergießen!Es gibt auch diese Antwort, die den Unterschied zwischen zerlegtem Code mit und ohne try
/ zeigt catch
.
Es scheint so offensichtlich , dass es ist ein Overhead , die offensichtlich beobachtbar in der Codegenerierung ist, und das Overhead scheint auch von Menschen , die Microsoft - Wert anerkannt zu werden! Dennoch wiederhole ich das Internet ...
Ja, es gibt Dutzende zusätzlicher MSIL-Anweisungen für eine triviale Codezeile, und das deckt nicht einmal die deaktivierten Optimierungen ab, so dass es sich technisch gesehen um eine Mikrooptimierung handelt.
Ich habe vor Jahren eine Antwort gepostet, die gelöscht wurde, da sie sich auf die Produktivität von Programmierern konzentrierte (die Makrooptimierung).
Dies ist bedauerlich, da hier und da keine Einsparung von wenigen Nanosekunden CPU-Zeit wahrscheinlich viele Stunden manueller Optimierung durch den Menschen ausgleichen wird. Wofür bezahlt Ihr Chef mehr: eine Stunde Ihrer Zeit oder eine Stunde bei laufendem Computer? Ab wann ziehen wir den Stecker und geben zu, dass es Zeit ist, nur einen schnelleren Computer zu kaufen ?
Natürlich sollten wir unsere Prioritäten optimieren , nicht nur unseren Code! In meiner letzten Antwort habe ich auf die Unterschiede zwischen zwei Codeausschnitten zurückgegriffen.
Verwenden von try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Nicht verwenden try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Betrachten Sie aus der Sicht eines Wartungsentwicklers, der Ihre Zeit eher verschwendet, wenn nicht bei der Profilerstellung / Optimierung (siehe oben), die ohne das try
/ catch
-Problem wahrscheinlich nicht einmal notwendig wäre , dann beim Scrollen durch Quellcode ... Eine davon hat vier zusätzliche Zeilen Müll auf dem Heizkessel!
Da immer mehr Felder in eine Klasse eingeführt werden, sammelt sich der gesamte Müll auf der Kesselplatte (sowohl im Quellcode als auch im zerlegten Code) weit über vernünftige Werte hinaus. Vier zusätzliche Zeilen pro Feld, und sie sind immer die gleichen Zeilen ... Wurde uns nicht beigebracht, uns nicht zu wiederholen? Ich nehme an, wir könnten das try
/ catch
hinter einer selbst gebrauten Abstraktion verstecken , aber ... dann könnten wir genauso gut Ausnahmen vermeiden (dh verwenden Int.TryParse
).
Dies ist nicht einmal ein komplexes Beispiel; Ich habe Versuche gesehen, neue Klassen in try
/ zu instanziieren catch
. Beachten Sie, dass der gesamte Code im Konstruktor möglicherweise von bestimmten Optimierungen ausgeschlossen wird, die sonst vom Compiler automatisch angewendet würden. Welchen besseren Weg gibt es, um die Theorie zu begründen, dass der Compiler langsam ist , im Gegensatz zum Compiler, der genau das tut, was ihm gesagt wurde ?
Unter der Annahme, dass der Konstruktor eine Ausnahme auslöst und dadurch ein Fehler ausgelöst wird, muss der schlechte Wartungsentwickler diese aufspüren. Das mag nicht so eine einfache Aufgabe sein, da im Gegensatz zu dem Spaghetti - Code des goto Alptraums, try
/ catch
können Verwirrungen in verursacht drei Dimensionen , wie sie den Stapel in nicht nur anderen Teile der gleichen Methode bewegen können, aber auch andere Klassen und Methoden , die alle vom Wartungsentwickler auf die harte Tour beobachtet werden ! Dennoch wird uns gesagt, dass "goto ist gefährlich", heh!
Am Ende erwähne ich, try
/ catch
hat seinen Vorteil, dass es Optimierungen deaktivieren soll ! Es ist, wenn Sie so wollen, eine Debugging-Hilfe ! Dafür wurde es entwickelt und es sollte als ...
Ich denke, das ist auch ein positiver Punkt. Es kann verwendet werden, um Optimierungen zu deaktivieren, die andernfalls sichere, vernünftige Algorithmen für die Nachrichtenübermittlung für Multithread-Anwendungen lähmen könnten, und um mögliche Rennbedingungen zu erfassen;) Dies ist ungefähr das einzige Szenario, das ich mir vorstellen kann, try / catch zu verwenden. Auch das hat Alternativen.
Welche Optimierungen tun try
, catch
und finally
deaktivieren?
AKA
Wie sind try
, catch
und finally
nützlich als Hilfsmittel Debugging?
Sie sind Schreibbarrieren. Dies kommt aus dem Standard:
12.3.3.13 Try-Catch-Anweisungen
Für eine Anweisung stmt des Formulars:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- Der definitive Zuweisungsstatus von v zu Beginn des Try-Blocks ist der gleiche wie der definitive Zuweisungsstatus von v zu Beginn von stmt .
- Der definitive Zuweisungsstatus von v zu Beginn von catch-block-i (für jedes i ) ist der gleiche wie der definitive Zuweisungsstatus von v zu Beginn von stmt .
- Der definitive Zuweisungsstatus von v am Endpunkt von stmt wird definitiv zugewiesen, wenn (und nur wenn) v definitiv am Endpunkt von try-block und jedem catch-Block-i zugewiesen wird (für jedes i von 1 bis n ).
Mit anderen Worten, am Anfang jeder try
Aussage:
try
Anweisung müssen abgeschlossen sein. Für den Start ist eine Thread-Sperre erforderlich, die sich zum Debuggen von Rennbedingungen eignet.try
Anweisung zugewiesen wurdenEine ähnliche Geschichte gilt für jede catch
Aussage; Angenommen try
, Sie weisen Ihrer Anweisung (oder einem Konstruktor oder einer Funktion, die sie aufruft, usw.), die Sie dieser ansonsten sinnlosen Variablen zuweisen (sagen wir, garbage=42;
) zu, diese Anweisung nicht zu entfernen, unabhängig davon, wie irrelevant sie für das beobachtbare Verhalten des Programms ist . Die Zuordnung muss abgeschlossen sein, bevor der catch
Block eingegeben wird.
Für das, was es wert ist, finally
erzählt eine ähnlich erniedrigende Geschichte:
12.3.3.14 Try-finally-Anweisungen
Für eine try- Anweisung stmt des Formulars:
try try-block finally finally-block
• Der definitive Zuweisungsstatus von v zu Beginn des Try-Blocks ist der gleiche wie der definitive Zuweisungsstatus von v zu Beginn von stmt .
• Der definitive Zuweisungsstatus von v zu Beginn des finally-Blocks ist der gleiche wie der definitive Zuweisungsstatus von v zu Beginn von stmt .
• Der definitive Zuweisungsstatus von v am Endpunkt von stmt wird definitiv zugewiesen, wenn (und nur wenn): o v wird definitiv am Endpunkt von try-block o v zugewiesenwird definitiv am Endpunkt des finally-Blocks zugewiesen. Wenn eine Kontrollflussübertragung (z. B. eine goto- Anweisung) durchgeführt wird, die innerhalb des try-Blocks beginnt und außerhalb des try-Blocks endet , wird v auch als definitiv zugewiesen betrachtet der Steuerfluss , wenn Übertragungs v definitiv am Endpunkt zugeordnet ist schließlich Block . (Dies ist nicht nur dann der Fall, wenn - wenn v aus einem anderen Grund bei dieser Kontrollflussübertragung definitiv zugewiesen ist, es immer noch als definitiv zugewiesen gilt.)
12.3.3.15 Try-catch-finally-Anweisungen
Definitive Zuordnungsanalyse für eine try - catch - finally - Anweisung des Formulars:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
getan wird , als ob die Aussage were a try - schließlich Anweisung eine umschließende try - catch - Anweisung:
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Ganz zu schweigen davon, dass sich eine häufig aufgerufene Methode auf das Gesamtverhalten der Anwendung auswirken kann.
Zum Beispiel betrachte ich die Verwendung von Int32.Parse in den meisten Fällen als eine schlechte Praxis, da es Ausnahmen für etwas auslöst, das sonst leicht abgefangen werden kann.
Um alles zu schließen, was hier geschrieben steht: 1) Verwenden Sie try..catch-Blöcke, um unerwartete Fehler abzufangen - fast keine Leistungseinbußen.
2) Verwenden Sie keine Ausnahmen für ausgenommene Fehler, wenn Sie dies vermeiden können.
Ich habe vor einiger Zeit einen Artikel darüber geschrieben, weil zu dieser Zeit viele Leute danach fragten. Sie finden es und den Testcode unter http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
Das Ergebnis ist, dass ein Try / Catch-Block nur einen geringen Overhead hat, aber so klein ist, dass er ignoriert werden sollte. Wenn Sie jedoch Try / Catch-Blöcke in Schleifen ausführen, die millionenfach ausgeführt werden, sollten Sie den Block nach Möglichkeit außerhalb der Schleife verschieben.
Das Hauptleistungsproblem bei Try / Catch-Blöcken besteht darin, dass Sie tatsächlich eine Ausnahme abfangen. Dies kann zu einer spürbaren Verzögerung Ihrer Anwendung führen. Wenn etwas schief geht, erkennen die meisten Entwickler (und viele Benutzer) die Pause natürlich als eine Ausnahme, die bald eintreten wird! Der Schlüssel hier ist, die Ausnahmebehandlung nicht für den normalen Betrieb zu verwenden. Wie der Name schon sagt, sind sie außergewöhnlich und Sie sollten alles tun, um zu verhindern, dass sie geworfen werden. Sie sollten sie nicht als Teil des erwarteten Ablaufs eines Programms verwenden, das ordnungsgemäß funktioniert.
Ich habe letztes Jahr einen Blogeintrag zu diesem Thema gemacht. Hör zu. Fazit ist, dass für einen Try-Block fast keine Kosten anfallen, wenn keine Ausnahme auftritt - und auf meinem Laptop lag eine Ausnahme bei etwa 36 μs. Das ist vielleicht weniger als erwartet, aber denken Sie daran, dass diese Ergebnisse auf einem flachen Stapel liegen. Auch die ersten Ausnahmen sind sehr langsam.
try
/ catch
zu viel? Heh heh), aber Sie scheinen mit der Sprachspezifikation und einigen MS-MVPs zu streiten, die auch Blogs zu diesem Thema geschrieben haben und Messungen bereitstellen im Gegenteil zu Ihrem Rat ... Ich bin offen für den Vorschlag, dass die von mir durchgeführten Recherchen falsch sind, aber ich muss Ihren Blogeintrag lesen, um zu sehen, was darin steht.
try-catch
Block gegen tryparse()
Methoden ab, aber das Konzept ist das gleiche.
Es ist wesentlich einfacher, Code zu schreiben, zu debuggen und zu verwalten, der frei von Compiler-Fehlermeldungen, Code-Analyse-Warnmeldungen und routinemäßig akzeptierten Ausnahmen ist (insbesondere Ausnahmen, die an einer Stelle ausgelöst und an einer anderen akzeptiert werden). Weil es einfacher ist, wird der Code im Durchschnitt besser geschrieben und weniger fehlerhaft sein.
Für mich ist dieser Programmierer- und Qualitätsaufwand das Hauptargument gegen die Verwendung von try-catch für den Prozessfluss.
Der Computeraufwand für Ausnahmen ist im Vergleich unbedeutend und im Hinblick auf die Fähigkeit der Anwendung, die tatsächlichen Leistungsanforderungen zu erfüllen, normalerweise gering.
Ich mag Hafthors Blog-Post sehr , und um meine zwei Cent zu dieser Diskussion hinzuzufügen, möchte ich sagen, dass es für mich immer einfach war, dass die DATENSCHICHT nur eine Art von Ausnahme auslöst (DataAccessException). Auf diese Weise weiß meine BUSINESS LAYER, welche Ausnahme zu erwarten ist, und fängt sie auf. Abhängig von weiteren Geschäftsregeln (dh wenn mein Geschäftsobjekt am Workflow teilnimmt usw.) kann ich dann eine neue Ausnahme auslösen (BusinessObjectException) oder fortfahren, ohne erneut zu werfen.
Ich würde sagen, zögern Sie nicht, try..catch zu verwenden, wann immer es notwendig ist, und verwenden Sie es mit Bedacht!
Diese Methode nimmt beispielsweise an einem Workflow teil ...
Bemerkungen?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
In Programming Languages Pragmatics von Michael L. Scott können wir lesen, dass die heutigen Compiler im allgemeinen Fall keinen Overhead hinzufügen, dh wenn keine Ausnahmen auftreten. So wird jede Arbeit in Kompilierungszeit gemacht. Wenn jedoch zur Laufzeit eine Ausnahme ausgelöst wird, muss der Compiler eine binäre Suche durchführen, um die richtige Ausnahme zu finden. Dies geschieht für jeden neuen Wurf, den Sie ausgeführt haben.
Aber Ausnahmen sind Ausnahmen und diese Kosten sind durchaus akzeptabel. Wenn Sie versuchen, die Ausnahmebehandlung ohne Ausnahmen durchzuführen und stattdessen Rückgabefehlercodes verwenden, benötigen Sie wahrscheinlich eine if-Anweisung für jede Unterroutine, und dies führt zu einem echten Echtzeit-Overhead. Sie wissen, dass eine if-Anweisung in einige Assembly-Anweisungen konvertiert wird, die jedes Mal ausgeführt werden, wenn Sie in Ihre Unterroutinen eingeben.
Entschuldigung für mein Englisch, hoffe es hilft dir. Diese Informationen basieren auf dem zitierten Buch. Weitere Informationen finden Sie in Kapitel 8.5 Ausnahmebehandlung.
Lassen Sie uns eine der größtmöglichen Kosten eines Try / Catch-Blocks analysieren, wenn er dort verwendet wird, wo er nicht verwendet werden sollte:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Und hier ist der ohne Versuch / Fang:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Ohne Berücksichtigung des unbedeutenden Leerraums könnte man feststellen, dass diese beiden äquivalenten Codeteile in Bytes fast genau dieselbe Länge haben. Letzteres enthält 4 Bytes weniger Einzug. Ist das etwas schlechtes?
Um die Verletzung zusätzlich zu beleidigen, entscheidet sich ein Schüler für eine Schleife, während die Eingabe als int analysiert werden kann. Die Lösung ohne try / catch könnte etwa so aussehen:
while (int.TryParse(...))
{
...
}
Aber wie sieht das bei der Verwendung von try / catch aus?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Try / Catch-Blöcke sind magische Methoden, um Einrückungen zu verschwenden, und wir wissen immer noch nicht einmal, warum sie fehlgeschlagen sind! Stellen Sie sich vor, wie sich die Person beim Debuggen fühlt, wenn Code nach einem schwerwiegenden logischen Fehler weiter ausgeführt wird, anstatt mit einem netten offensichtlichen Ausnahmefehler anzuhalten. Try / Catch-Blöcke sind die Datenvalidierung / -hygiene eines faulen Mannes.
Eine der geringeren Kosten besteht darin, dass Try / Catch-Blöcke tatsächlich bestimmte Optimierungen deaktivieren: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Ich denke, das ist auch ein positiver Punkt. Es kann verwendet werden, um Optimierungen zu deaktivieren, die andernfalls sichere, vernünftige Algorithmen für die Nachrichtenübermittlung für Multithread-Anwendungen lähmen könnten, und um mögliche Rennbedingungen zu erfassen;) Dies ist ungefähr das einzige Szenario, das ich mir vorstellen kann, try / catch zu verwenden. Auch das hat Alternativen.
Int.Parse
für Int.TryParse
.