Jest ostatecznie drogi

14

W przypadku kodu, w którym należy wykonać czyszczenie zasobu przed wyjściem z funkcji, istnieje znaczna różnica w wydajności między tymi 2 sposobami wykonania tego.

  1. Czyszczenie zasobu przed każdym zwrotem

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Czyszczenie zasobu w końcu bloku

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

Zrobiłem kilka podstawowych testów w przykładowych programach i wydaje się, że nie ma dużej różnicy. Tak bardzo wolę finallysposób na zrobienie tego - ale zastanawiałem się, czy to wpłynie na wydajność w dużym projekcie.

użytkownik93353
źródło
3
Naprawdę nie warto pisać jako odpowiedzi, ale nie. Jeśli używasz Javy, która oczekuje, że wyjątki będą domyślną strategią obsługi błędów, powinieneś użyć try / catch / wreszcie - język jest zoptymalizowany pod kątem używanego wzorca.
Jonathan Rich
1
@JathanathanRich: jak to możliwe?
haylem
2
Napisz kod czysto w taki sposób, aby wyrażał to, co próbujesz osiągnąć. Zoptymalizuj później, gdy wiesz, że masz problem - nie unikaj funkcji językowych, ponieważ uważasz, że możesz ogolić kilka milisekund z czegoś, co nie jest wolne.
Sean McSomething,
@mikeTheLiar - Jeśli masz na myśli, że powinienem pisać if(!cond), to java mnie do tego zmusiła. W C ++ w ten sposób piszę kod zarówno logiczne, jak i dla innych typów - tj int x; if(!x). Ponieważ java pozwala mi używać tego tylko do booleans, całkowicie przestałem używać if(cond)& if(!cond)in java.
user93353,
1
@ user93353 to mnie smuci. Wolałbym raczej widzieć (someIntValue != 0)niż porównywać niż oceniać booleany. To mi pachnie i przerabiam to natychmiast, gdy widzę to na wolności.
MikeTheLiar

Odpowiedzi:

23

Jak wskazano w Jak powolne są wyjątki Java? widać, że powolność try {} catch {}mieści się w obrębie samego wyjątku.

Utworzenie wyjątku spowoduje pobranie całego stosu wywołań z środowiska wykonawczego i to tam jest koszt. Jeżeli nie tworzysz wyjątek, to tylko bardzo niewielkie zwiększenie czasu.

W przykładzie podanym w tym pytaniu nie ma żadnych wyjątków, nie można oczekiwać spowolnienia ich tworzenia - nie są tworzone. Zamiast tego chodzi tutaj o try {} finally {}obsługę dezalokacji zasobów w bloku wreszcie.

Tak więc, aby odpowiedzieć na pytanie, nie, nie ma rzeczywistych wydatków na środowisko uruchomieniowe w try {} finally {}strukturze, która nie używa wyjątków (nie jest to niespotykane, jak widać). Co jest ewentualnie drogie jest utrzymanie czasu, gdy jeden odczytuje kod i widzi to nie typowy styl kodu i koder musi uzyskać ich umysł dookoła, że coś innego się dzieje w tej metodzie od wyprodukowania returnprzed powrotem do poprzedniej rozmowy.


Jak już wspomniano, utrzymanie jest argumentem na oba sposoby osiągnięcia tego celu. Dla przypomnienia, moim wyborem byłoby podejście ostateczne.

Zastanów się, jaki czas poświęcić na nauczenie kogoś nowej struktury językowej. Widzenie try {} finally {}nie jest czymś, co często widuje się w kodzie Java i dlatego może być mylące dla ludzi. Na naukę nieco bardziej zaawansowanych struktur w Javie jest pewien czas konserwacji, niż ludzie znają.

finally {}Blok zawsze działa. I dlatego powinieneś go użyć. Weź również pod uwagę czas konserwacji debugowania podejścia, które nie jest ostateczne, gdy ktoś zapomni o wylogowaniu w odpowiednim czasie lub zadzwoni o nim w niewłaściwym czasie, lub zapomni o powrocie / wyjściu po wywołaniu, aby wywołać go dwukrotnie. Istnieje tak wiele możliwych błędów, że użycie try {} finally {}uniemożliwia ich użycie .

Ważenie tych dwóch kosztów jest kosztowne w czasie konserwacji, aby nie stosować tego try {} finally {}podejścia. Chociaż ludzie mogą zastanawiać się, ile ułamków milisekund lub dodatkowych instrukcji JVM try {} finally {}blok jest porównywany z inną wersją, należy również wziąć pod uwagę godziny spędzone na debugowaniu mniej niż idealnego sposobu rozwiązania problemu dezalokacji zasobów.

Najpierw napisz utrzymywalny kod, najlepiej w taki sposób, aby uniknąć późniejszego napisania błędów.

Społeczność
źródło
1
Edycja sugerowana przez anona brzmiała następująco: „spróbuj / w końcu oferuje gwarancje, których inaczej nie miałbyś w odniesieniu do wyjątków czasu wykonywania. Niezależnie od tego, czy ci się to podoba, czy nie, każda linia może generować wyjątek” - Ja: co nie jest do końca prawdą . Struktura podana w pytaniu nie ma żadnych dodatkowych wyjątków, które spowolniłyby wydajność kodu. Nie ma dodatkowego wpływu na wydajność poprzez owijanie try / last wokół bloku w porównaniu do bloku non-try / last.
2
Co może być jeszcze droższe w utrzymaniu niż zrozumienie tego zastosowania try-wreszcie, to konieczność utrzymania kilku bloków czyszczących. Teraz przynajmniej wiesz, że jeśli potrzebne jest dodatkowe sprzątanie, jest tylko jedno miejsce, aby je umieścić.
Bart van Ingen Schenau
@BartvanIngenSchenau Rzeczywiście. Wypróbowanie jest prawdopodobnie najlepszym sposobem na poradzenie sobie z tym, po prostu nie jest tak powszechne w strukturze, którą widziałem w kodzie - ludzie mają dość problemów z próbą zrozumienia próby złapania w końcu i obsługi wyjątków w ogóle ... ale spróbuj -finaly bez haczyka? Co? Ludzie naprawdę tego nie rozumieją . Jak zasugerowałeś, lepiej jest zrozumieć strukturę języka niż próbować jej unikać i robić rzeczy źle.
Chciałem przede wszystkim powiedzieć, że alternatywa do wypróbowania w końcu też nie jest darmowa. Chciałbym móc jeszcze raz wyrazić poparcie dla twojego dodatku na temat kosztów.
Bart van Ingen Schenau,
5

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

Przyjęta odpowiedź na to pytanie pokazuje, że zawinięcie wywołania funkcji w bloku try catch kosztuje mniej niż 5% w porównaniu z samym wywołaniem funkcji. Faktyczne zgłoszenie i wyłapanie wyjątku spowodowało, że środowisko wykonawcze wygenerowało balon ponad 66-krotnie w stosunku do samego wywołania funkcji. Jeśli więc spodziewasz się, że projekt wyjątku będzie regularnie zgłaszany, to spróbuję go ominąć (w odpowiednio wyprofilowanym kodzie krytycznym pod względem wydajności), jednak jeśli wyjątkowa sytuacja jest rzadka, nie jest to wielka sprawa.

kamieniste
źródło
3
„jeśli wyjątkowa sytuacja jest rzadka” - cóż, jest tu tautologia. Wyjątek oznacza rzadki i właśnie w ten sposób należy stosować wyjątki. Nigdy nie powinien być częścią procesu projektowania lub kontroli.
Jesse C. Slicer
1
Wyjątki niekoniecznie są w praktyce rzadkie, to tylko przypadkowy efekt uboczny. Na przykład, jeśli masz złe interfejsy, ludzie mogą powodować przepływ przypadków wyjątków częściej niż normalny
Esailija,
2
W kodzie pytania nie ma wyjątków.
2
@ JesseC.Slicer Istnieją języki, w których często zdarza się, że są używane jako kontrola przepływu. Jako przykład podczas iteracji czegokolwiek w pythonie iterator zgłasza wyjątek stopiteracji, gdy jest to zrobione. Również w OCaml ich standardowa biblioteka wydaje się bardzo „rzucać szczęśliwą”, rzuca w wielu sytuacjach, które nie są rzadkie (jeśli masz mapę i spróbuj poszukać czegoś, co nie istnieje na mapie, którą rzuca ).
stonemetal
@stonemetal Podobnie jak w przypadku Python, z KeyError
Izkata
4

Zamiast optymalizacji - pomyśl o tym, co robi Twój kod.

Dodanie wreszcie bloku jest inną implementacją - oznacza to, że wylogujesz się przy każdym wyjątku.

W szczególności - jeśli logowanie zgłosi wyjątek, zachowanie w drugiej funkcji będzie inne niż pierwsze.

Jeśli logowanie i wylogowanie nie są częścią zachowania funkcji, sugerowałbym, że metoda powinna zrobić jedną rzecz dobrze:

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

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

            dootherstuff();

    }

i powinien być w funkcji, która jest już zamknięta w kontekście, który zarządza logowaniem / wylogowaniem, ponieważ wydają się one zasadniczo różnić od tego, co robi główny kod.

Twój post jest oznaczony jako Java, którego nie znam zbyt dobrze, ale w .net użyłbym do tego bloku używającego:

to znaczy:

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

Kontekst logowania ma metodę Dispose, która wyloguje się i wyczyści zasoby.

Edycja: Właściwie nie odpowiedziałem na pytanie!

Nie mam żadnych wymiernych dowodów, ale nie spodziewałbym się, że będzie to droższe lub z pewnością niewystarczająco drogie, aby było warte przedwczesnej optymalizacji.

Pozostawiam resztę odpowiedzi, ponieważ chociaż nie odpowiadam na pytanie, myślę, że jest to pomocne dla PO.

Michael
źródło
1
Aby uzupełnić odpowiedź, Java 7 wprowadziła instrukcję try-with-resources, która byłaby podobna do twojej usingkonstrukcji .net , zakładając, że dany zasób implementuje AutoCloseableinterfejs.
haylem
Chciałbym nawet usunąć tę retzmienną, która jest właśnie przypisana dwukrotnie, aby sprawdzić jedną linię później. Zrób to if (dosomething() == false) return;.
ott--
Zgodziłem się, ale chciałem zachować kod OP w sposób, w jaki został podany.
Michael
@ ott-- Zakładam, że kod OP jest uproszczeniem ich przypadku użycia - na przykład mogą mieć coś podobnego ret2 = doSomethingElse(ret1), ale to nie dotyczy pytania, więc zostało usunięte.
Izkata,
if (dosomething() == false) ...jest zły kod. Użyj bardziej inaktywnegoif (!dosomething()) ...
Steffen Heil
1

Założenie: rozwijasz się w kodzie C #.

Szybka odpowiedź jest taka, że ​​nie ma znaczącego spadku wydajności przy użyciu bloków try / last. Występuje uderzenie wydajności, gdy rzucasz wyjątkami.

Dłuższą odpowiedzią jest przekonanie się. Jeśli spojrzysz na wygenerowany kod CIL ( np. Reflektor z czerwoną bramką) , zobaczysz wygenerowane instrukcje CIL i zobaczysz w ten sposób wpływ.

Michael Shaw
źródło
11
Pytanie jest oznaczone java .
Joachim Sauer
Dobrze zauważony! ;)
Michael Shaw,
3
Ponadto metody nie są kapitalizowane, chociaż może to być po prostu dobry gust.
Erik Reppen,
wciąż dobra odpowiedź, jeśli pytanie brzmiało c # :)
PhillyNJ,
-2

Jak inni już wspomniano, kod Ttese mają różne wyniki, jeśli jedna dosomethingelselub dootherstuffwyjątek.

I tak, każde wywołanie funkcji może zgłosić wyjątek, przynajmniej StackOVerflowError! Chociaż rzadko jest możliwe prawidłowe ich obsługiwanie (ponieważ jakakolwiek wywoływana funkcja czyszczenia prawdopodobnie będzie miała ten sam problem, w niektórych przypadkach (takich jak na przykład wdrażanie blokad) ważne jest, aby nawet poprawnie to obsłużyć ...

Steffen Heil
źródło
2
wydaje się, że nie oferuje to nic istotnego w porównaniu do poprzednich 6 odpowiedzi
komara