Ist probier mal teuer

14

Im Falle von Code, bei dem Sie vor dem Beenden einer Funktion eine Ressourcenbereinigung durchführen müssen, besteht ein wesentlicher Leistungsunterschied zwischen diesen beiden Methoden.

  1. Bereinigen Sie die Ressource vor jeder return-Anweisung

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Bereinigen Sie die Ressource in einem finally-Block

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

Ich habe einige grundlegende Tests in Beispielprogrammen durchgeführt und es scheint keinen großen Unterschied zu geben. Ich bevorzuge das so sehr finally- aber ich habe mich gefragt, ob es bei einem großen Projekt zu Leistungseinbußen kommen wird.

user93353
quelle
3
Es lohnt sich nicht, eine Antwort zu schreiben, aber nein. Wenn Sie Java verwenden, das Ausnahmen als Standardstrategie für die Fehlerbehandlung erwartet, sollten Sie try / catch / finally verwenden. Die Sprache ist für das von Ihnen verwendete Muster optimiert.
Jonathan Rich
1
@ JonathanRich: wie so?
Haylem
2
Schreiben Sie den Code so, dass er das ausdrückt, was Sie erreichen möchten. Optimieren Sie später, wenn Sie wissen, dass Sie ein Problem haben. Vermeiden Sie Sprachfunktionen nicht, da Sie der Meinung sind, dass Sie sich einige Millisekunden von etwas abschneiden könnten, das nicht unbedingt langsam ist.
Sean McSomething
@mikeTheLiar - Wenn du meinst, dass ich schreiben soll if(!cond), dann ist es Java, das mich dazu gebracht hat. In C ++ schreibe ich so Code sowohl für Boolesche als auch für andere Typen - dh int x; if(!x). Da ich dies mit Java nur für verwenden darf booleans, habe ich die Verwendung von if(cond)& if(!cond)in Java vollständig eingestellt .
User93353
1
@ user93353 das macht mich traurig. Ich würde viel lieber sehen, (someIntValue != 0)als zu vergleichen, als Boolesche Werte zu bewerten. Das riecht für mich und ich überarbeite es sofort, wenn ich es in freier Wildbahn sehe.
MikeTheLiar

Antworten:

23

Wie in Wie langsam sind Java-Ausnahmen angegeben? man kann sehen, dass die Langsamkeit von try {} catch {}in der Ausnahmebedingung selbst liegt.

Wenn Sie eine Ausnahme erstellen, wird der gesamte Aufrufstapel aus der Laufzeit abgerufen, und hier liegen die Kosten. Wenn Sie nicht eine Ausnahme zu schaffen, dann ist dies nur sehr leicht eine Erhöhung der Zeit.

In dem Beispiel in dieser Frage gibt es keine Ausnahmen, man würde keine Verlangsamung erwarten, wenn man sie erstellt - sie werden nicht erstellt. Stattdessen soll hier die try {} finally {}Freigabe von Ressourcen innerhalb des finally-Blocks behandelt werden.

Um die Frage zu beantworten, nein, es gibt keinen wirklichen Laufzeitaufwand innerhalb eines try {} finally {} Struktur, die keine Ausnahmen verwendet (wie man sieht ist dies nicht ungewöhnlich). Was ist möglicherweise teuer ist die Wartungszeit , wenn man den Code liest und sieht diese nicht typischen Code - Stil und die Codierer haben ihren Geist zu umgehen , dass etwas anderes in diesem Verfahren nach dem passiert , returnbevor sie an den vorherigen Anruf zurück.


Wie bereits erwähnt, ist die Wartung ein Argument für beide Möglichkeiten. Für die Aufzeichnung, nach Abwägung, wäre meine Präferenz der endgültige Ansatz.

Berücksichtigen Sie die Wartungszeit, um jemandem eine neue Sprachstruktur beizubringen. Sehen try {} finally {}ist etwas, was man im Java-Code oft sieht und daher für die Leute verwirrend sein kann. Es gibt einen gewissen Grad an Wartungszeit, um etwas fortgeschrittenere Strukturen in Java zu erlernen, als die Leute mit dem Sehen vertraut sind.

Der finally {}Block immer läuft . Und deshalb sollten Sie es verwenden. Berücksichtigen Sie auch die Wartungszeit für das Debuggen des nicht endgültigen Ansatzes, wenn jemand vergisst, sich zum richtigen Zeitpunkt abzumelden, oder es zur falschen Zeit aufruft oder vergisst, nach dem Aufruf zurückzukehren / zu beenden, sodass es zweimal aufgerufen wird. Es gibt so viele mögliche Fehler damit, dass die Verwendung von try {} finally {}unmöglich macht, zu haben.

Wenn diese beiden Kosten abgewogen werden, ist es in der Wartungszeit teuer, das nicht zu verwendentry {} finally {} Ansatz . Während die Leute darüber streiten können, wie viele Bruchteil-Millisekunden oder zusätzliche JVM-Anweisungen der try {} finally {}Block mit der anderen Version verglichen wird, muss man auch die Stunden in Betracht ziehen, die für das Debuggen der weniger als idealen Methode zur Lösung der Ressourcendeallokation aufgewendet wurden.

Schreiben Sie den wartbaren Code zuerst und vorzugsweise so, dass später keine Fehler mehr geschrieben werden.

Gemeinschaft
quelle
1
Eine nicht vorgeschlagene Änderung lautete wie folgt: "try / finally bietet Garantien, die Sie in Bezug auf Laufzeitausnahmen nicht anders hätten. Egal, ob Sie möchten oder nicht, jede Zeile kann praktisch eine Ausnahme auslösen." . Die in der Frage angegebene Struktur enthält keine zusätzlichen Ausnahmen, die die Leistung des Codes verlangsamen würden. Es gibt keine zusätzlichen Leistungseinbußen, wenn ein try / finally-Befehl im Vergleich zum non-try / finally-Befehl um den Block gewickelt wird.
2
Was in der Wartung noch teurer sein könnte, als diese Verwendung von try-finally zu verstehen, ist, mehrere Bereinigungsblöcke warten zu müssen. Zumindest wissen Sie jetzt, dass es nur einen Ort gibt, an dem zusätzliche Aufräumarbeiten erforderlich sind.
Bart van Ingen Schenau
@BartvanIngenSchenau In der Tat. Ein try-finally ist wahrscheinlich die beste Möglichkeit, damit umzugehen. Es ist nur nicht so häufig, wie ich es in Code gesehen habe. Die Leute haben genug Mühe, try catch und die Ausnahmebehandlung im Allgemeinen zu verstehen. Aber try -Finale ohne einen Haken? Was? Die Leute verstehen es wirklich nicht . Wie Sie vorgeschlagen haben, ist es besser, die Sprachstruktur zu verstehen, als zu versuchen, sie zu umgehen und die Dinge schlecht zu machen.
Ich wollte vor allem darauf aufmerksam machen, dass die Alternative zu try-finally auch nicht kostenlos ist. Ich wünschte, ich könnte eine weitere Gegenstimme für Ihre Hinzufügung zu den Kosten abgeben.
Bart van Ingen Schenau
5

/programming/299068/how-slow-are-java-exceptions

Die akzeptierte Antwort auf diese Frage zeigt, dass das Einschließen eines Funktionsaufrufs in einen Try-Catch-Block weniger als 5% gegenüber einem bloßen Funktionsaufruf kostet. Tatsächlich verursachte das Auslösen und Abfangen der Ausnahme, dass die Laufzeit mehr als das 66-fache des bloßen Funktionsaufrufs erreichte. Wenn Sie also erwarten, dass das Ausnahmedesign regelmäßig ausgelöst wird, würde ich versuchen, es zu vermeiden (in ordnungsgemäß profiliertem, leistungskritischem Code). Wenn die Ausnahmesituation jedoch selten ist, ist es keine große Sache.

Steinmetalle
quelle
3
"Wenn die Ausnahmesituation selten ist" - genau dort gibt es eine Tautologie. Außergewöhnlich bedeutet selten und genau so sollten Ausnahmen verwendet werden. Niemals Teil des Entwurfs oder Kontrollflusses sein.
Jesse C. Slicer
1
Ausnahmen sind in der Praxis nicht unbedingt selten, es handelt sich nur um zufällige Nebenwirkungen. Wenn Sie z. B. eine schlechte Benutzeroberfläche haben, können Benutzer die Ausnahme häufiger als normal verwenden
Esailija,
2
Der Code der Frage enthält keine Ausnahmen.
2
@ JesseC.Slicer Es gibt Sprachen, in denen sie nicht selten als Flusskontrolle verwendet werden. Als Beispiel für die Iteration in Python löst der Iterator eine Stopiter-Ausnahme aus, wenn dies abgeschlossen ist. Auch in OCaml scheint die Standardbibliothek sehr "throw happy" zu sein. Sie löst eine große Anzahl von Situationen aus, die nicht selten sind (wenn Sie eine Karte haben und versuchen, etwas nachzuschlagen, das nicht in der von ihr ausgelösten Karte vorhanden ist) ).
Stonemetal
@stonemetal Wie es ein Python-Diktat tut, mit einem KeyError
Izkata
4

Anstatt zu optimieren - überlegen Sie, was Ihr Code tut.

Das Hinzufügen des finally-Blocks ist eine andere Implementierung. Dies bedeutet, dass Sie sich bei jeder Ausnahme abmelden.

Insbesondere - wenn die Anmeldung eine Ausnahme auslöst, unterscheidet sich das Verhalten in Ihrer zweiten Funktion von Ihrem ersten.

Wenn An- und Abmeldung nicht zum Funktionsverhalten gehören, würde ich vorschlagen, dass die Methode eine Sache und eine Sache gut macht:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

und sollte sich in einer Funktion befinden, die bereits in einem Kontext enthalten ist, der das Anmelden / Abmelden verwaltet, da diese sich grundlegend von dem zu unterscheiden scheinen, was Ihr Hauptcode tut.

Dein Beitrag ist mit Java markiert, mit dem ich nicht besonders vertraut bin, aber in .net würde ich dafür einen using-Block verwenden:

dh:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

Der Anmeldekontext verfügt über eine Dispose-Methode, mit der die Ressourcen abgemeldet und bereinigt werden.

Edit: Ich habe die Frage nicht wirklich beantwortet!

Ich habe keine messbaren Beweise, aber ich würde nicht erwarten, dass dies teurer oder mit Sicherheit nicht signifikant genug ist, um eine vorzeitige Optimierung zu verdienen.

Ich werde den Rest meiner Antwort verlassen, da ich die Frage zwar nicht beantworte, sie aber für das OP hilfreich finde.

Michael
quelle
1
Um Ihre Antwort zu vervollständigen, hat Java 7 eine try-with-resources-Anweisung eingeführt, die Ihrem .net- usingKonstrukt ähnelt , sofern die betreffende Ressource die AutoCloseableSchnittstelle implementiert .
Haylem
Ich würde sogar diese retVariable entfernen , die nur zweimal zugewiesen wird, um eine Zeile später überprüft zu werden. Mach es if (dosomething() == false) return;.
ott--
Einverstanden, aber ich wollte den OP-Code so behalten, wie er bereitgestellt wurde.
Michael
@ott-- Ich gehe davon aus, dass der OP-Code eine Vereinfachung ihres Anwendungsfalls darstellt - zum Beispiel könnten sie etwas ähnlicheres haben ret2 = doSomethingElse(ret1), aber das ist für die Frage nicht relevant, sodass sie entfernt wurde.
Izkata
if (dosomething() == false) ...ist schlechter Code. Verwenden Sie die intuitivereif (!dosomething()) ...
Steffen Heil
1

Annahme: Sie entwickeln in C # -Code.

Die schnelle Antwort lautet, dass die Verwendung von try / finally-Blöcken keine signifikanten Leistungseinbußen zur Folge hat. Es gibt einen Leistungstreffer, wenn Sie Ausnahmen werfen und fangen.

Die längere Antwort ist, für sich selbst zu sehen. Wenn Sie sich den generierten zugrunde liegenden CIL-Code ansehen ( z. B. Red-Gate-Reflektor) , können Sie die generierten zugrunde liegenden CIL-Anweisungen und die Auswirkungen auf diese Weise anzeigen.

Michael Shaw
quelle
11
Die Frage ist Java markiert .
Joachim Sauer
Gut erkannt! ;)
Michael Shaw
3
Auch Methoden werden nicht großgeschrieben, obwohl das nur gut schmecken könnte.
Erik Reppen
Immer noch eine gute Antwort, wenn die Frage c # war :)
PhillyNJ
-2

Wie bereits erwähnt, führt dieser Code zu unterschiedlichen Ergebnissen, wenn entweder dosomethingelseoderdootherstuff eine Ausnahme eine Ausnahme .

Und ja, jeder Funktionsaufruf kann eine Ausnahme auslösen, zumindest ein StackOVerflowError! Obwohl es selten möglich ist, diese korrekt zu behandeln (da jede aufgerufene Bereinigungsfunktion wahrscheinlich dasselbe Problem hat, ist es in einigen Fällen (wie zum Beispiel beim Implementieren von Sperren) entscheidend, diese auch korrekt zu behandeln ...

Steffen Heil
quelle
2
dies scheint nicht zu bieten alles wesentliche über vor 6 Antworten
gnat