Pytanie: Czy obsługa wyjątków w Javie jest naprawdę powolna?
Konwencjonalna wiedza, a także wiele wyników Google, mówi, że wyjątkowa logika nie powinna być używana do normalnego przepływu programów w Javie. Zazwyczaj podaje się dwa powody:
- jest naprawdę wolny - nawet o rząd wielkości wolniejszy niż zwykły kod (podane przyczyny są różne),
i
- jest bałagan, ponieważ ludzie oczekują, że w wyjątkowym kodzie będą obsługiwane tylko błędy.
To pytanie dotyczy nr 1.
Na przykład ta strona opisuje obsługę wyjątków Java jako „bardzo powolną” i wiąże powolność z tworzeniem ciągu komunikatu o wyjątku - „ten ciąg jest następnie wykorzystywany do tworzenia zgłaszanego obiektu wyjątku. To nie jest szybkie”. Artykuł „ Efektywna obsługa wyjątków w Javie” mówi, że „przyczyną tego jest aspekt tworzenia wyjątków obsługi wyjątków, co powoduje, że generowanie wyjątków jest z natury powolne”. Innym powodem jest to, że spowalnia to generowanie śladu stosu.
Moje testy (przy użyciu Java 1.6.0_07, Java HotSpot 10.0, w 32-bitowym systemie Linux) wskazują, że obsługa wyjątków nie jest wolniejsza niż zwykły kod. Próbowałem uruchomić metodę w pętli, która wykonuje jakiś kod. Na końcu metody używam wartości logicznej, aby wskazać, czy zwrócić, czy rzucić . W ten sposób faktyczne przetwarzanie jest takie samo. Próbowałem uruchamiać metody w różnych zamówieniach i uśredniać czas testu, sądząc, że mogło to być rozgrzewanie JVM. We wszystkich moich testach rzut był co najmniej tak szybki jak powrót, jeśli nie szybszy (do 3,1% szybciej). Jestem całkowicie otwarty na możliwość, że moje testy były niepoprawne, ale nie widziałem niczego na drodze do próbki kodu, porównań testów lub wyników w ciągu ostatniego roku lub dwóch, które pokazują, że obsługa wyjątków w Javie jest faktycznie powolny.
To, co prowadzi mnie na tę ścieżkę, to interfejs API, którego musiałem użyć, który wyrzucił wyjątki w ramach normalnej logiki sterowania. Chciałem je poprawić, ale teraz mogę tego nie robić. Czy zamiast tego będę musiał pochwalić ich myślenie naprzód?
W artykule Efektywna obsługa wyjątków Java w kompilacji just-in-time autorzy sugerują, że obecność samych procedur obsługi wyjątków, nawet jeśli nie zostaną zgłoszone żadne wyjątki, wystarcza, aby kompilator JIT nie zoptymalizował poprawnie kodu, spowalniając go w ten sposób . Nie przetestowałem jeszcze tej teorii.
źródło
exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow
- podając pełne i wyczerpujące wyjaśnienie, dlaczego. I był facetem, który napisał lib Java. Dlatego to on definiuje kontrakt API klas. / zgadzam się z Billem K. w tej sprawie.Odpowiedzi:
Zależy to od sposobu wdrożenia wyjątków. Najprostszym sposobem jest użycie setjmp i longjmp. Oznacza to, że wszystkie rejestry procesora są zapisywane na stosie (co zajmuje już trochę czasu) i być może trzeba utworzyć inne dane ... wszystko to dzieje się już w instrukcji try. Instrukcja throw musi rozwinąć stos i przywrócić wartości wszystkich rejestrów (i możliwe inne wartości w maszynie wirtualnej). Więc try i throw są równie powolne i to jest dość powolne, jednak jeśli nie zostanie zgłoszony żaden wyjątek, wyjście z bloku try nie zajmuje w większości przypadków żadnego czasu (ponieważ wszystko jest umieszczane na stosie, który czyści się automatycznie, jeśli metoda istnieje).
Sun i inni uznali, że jest to prawdopodobnie nieoptymalne i oczywiście maszyny wirtualne stają się coraz szybsze w miarę upływu czasu. Istnieje inny sposób implementacji wyjątków, który sprawia, że próba sama w sobie jest błyskawiczna (w rzeczywistości nic się nie dzieje podczas próby w ogóle - wszystko, co musi się zdarzyć, jest już zrobione, gdy klasa jest ładowana przez maszynę wirtualną) i powoduje, że rzut nie jest tak wolny . Nie wiem, która JVM korzysta z tej nowej, lepszej techniki ...
... ale piszesz w Javie, więc Twój kod będzie później działał tylko na jednej maszynie JVM w jednym określonym systemie? Ponieważ jeśli może kiedykolwiek działać na innej platformie lub w dowolnej innej wersji JVM (być może dowolnego innego dostawcy), kto twierdzi, że korzysta także z szybkiej implementacji? Szybka jest bardziej skomplikowana niż wolna i nie jest łatwo możliwa we wszystkich systemach. Chcesz pozostać przenośny? Nie polegaj więc na tym, że wyjątki są szybkie.
To także robi dużą różnicę w tym, co robisz w bloku try. Jeśli otworzysz blok try i nigdy nie wywołasz żadnej metody z tego bloku try, blok try będzie bardzo szybki, ponieważ JIT może wtedy traktować rzut jak zwykłe goto. Nie musi on zapisywać stanu stosu, ani nie musi się rozwijać stosu, jeśli zostanie zgłoszony wyjątek (wystarczy przeskoczyć do programów obsługi przechwytywania). Jednak to nie jest to, co zwykle robisz. Zwykle otwierasz blok try, a następnie wywołujesz metodę, która może zgłosić wyjątek, prawda? I nawet jeśli użyjesz tylko bloku try w swojej metodzie, jaki to będzie sposób, który nie wywołuje żadnej innej metody? Czy to po prostu obliczy liczbę? Do czego więc potrzebujesz wyjątków? Istnieją znacznie bardziej eleganckie sposoby regulowania przebiegu programu. W zasadzie wszystko inne niż prosta matematyka,
Zobacz następujący kod testowy:
Wynik:
Spowolnienie z bloku try jest zbyt małe, aby wykluczyć mylące czynniki, takie jak procesy w tle. Ale blok catch zabił wszystko i sprawił, że 66 razy wolniej!
Jak powiedziałem, wynik nie będzie taki zły, jeśli umieścisz try / catch i wyrzucisz wszystko w ramach tej samej metody (metoda3), ale jest to specjalna optymalizacja JIT, na której nie polegałbym. I nawet podczas korzystania z tej optymalizacji rzut jest nadal dość wolny. Więc nie wiem, co próbujesz tutaj zrobić, ale jest zdecydowanie lepszy sposób na zrobienie tego niż użycie try / catch / throw.
źródło
nanoTime()
wymaga Java 1.5 i miałem tylko Java 1.4 w systemie, którego użyłem do napisania powyższego kodu. Nie odgrywa też dużej roli w praktyce. Jedyna różnica między nimi polega na tym, że jedna jest nanosekunda, a druga milisekunda i nananoTime
to nie mają wpływu manipulacje zegarem (które są nieistotne, chyba że ty lub proces systemowy zmodyfikuje zegar systemowy dokładnie w momencie uruchomienia kodu testowego). Ogólnie jednak masz rację,nanoTime
jest oczywiście lepszym wyborem.try
blokiem, ale niethrow
. Twójthrow
test rzuca wyjątki w 50% przypadkówtry
. To wyraźnie sytuacja, w której awaria nie jest wyjątkowa . Obniżenie tego do zaledwie 10% znacznie obniża wydajność. Problem z tego rodzaju testem polega na tym, że zachęca on ludzi do całkowitego zaprzestania stosowania wyjątków. Korzystanie z wyjątków w celu wyjątkowej obsługi przypadków działa znacznie lepiej niż pokazuje test.return
. Pozostawia metodę gdzieś w środku ciała, może nawet w środku operacji (która do tej pory zakończyła się tylko o 50%), acatch
blok może mieć 20 ramek stosu w górę (metoda matry
blok, wywołujący metodę1, który wywołuje metodę2, która wywołuje mehtod3, ..., aw metodzie20 w środku operacji zgłaszany jest wyjątek). Stos musi być odwijany o 20 klatek w górę, wszystkie niedokończone operacje muszą być cofnięte (operacje nie mogą być w połowie wykonane), a rejestry procesora muszą być w stanie czystym. To wszystko pochłania czas.Do Twojej wiadomości rozszerzyłem eksperyment, który przeprowadziła Mecki:
Pierwsze 3 są takie same jak Mecki (mój laptop jest oczywiście wolniejszy).
metoda4 jest identyczna z metodą3, z tą różnicą, że tworzy
new Integer(1)
raczej działanie niż działaniethrow new Exception()
.metoda5 jest podobna do metody3, z tą różnicą, że tworzy
new Exception()
bez rzucania.metoda6 jest podobna do metody3, z tą różnicą, że generuje wstępnie utworzony wyjątek (zmienną instancji), zamiast tworzyć nowy.
W Javie znaczną część kosztów zgłaszania wyjątku stanowi czas gromadzenia śladu stosu, który występuje, gdy tworzony jest obiekt wyjątku. Rzeczywisty koszt zgłoszenia wyjątku, choć duży, jest znacznie niższy niż koszt utworzenia wyjątku.
źródło
Aleksey Shipilëv przeprowadził bardzo dokładną analizę, w której porównał wyjątki Javy w różnych kombinacjach warunków:
Porównuje je także do sprawdzania kodu błędu na różnych poziomach częstotliwości błędów.
Wnioski (cytowane dosłownie z jego postu) były następujące:
Naprawdę wyjątkowe wyjątki są pięknie występujące. Jeśli użyjesz ich zgodnie z przeznaczeniem i przekażesz tylko naprawdę wyjątkowe przypadki spośród przeważnie dużej liczby nietypowych spraw obsługiwanych przez zwykły kod, wówczas użycie wyjątków jest wygraną wydajności.
Koszty wydajności wyjątków mają dwa główne elementy: konstrukcję śledzenia stosu, gdy wystąpi wyjątek, oraz rozwijanie stosu podczas rzucania wyjątku.
Koszty budowy śledzenia stosu są proporcjonalne do głębokości stosu w momencie wystąpienia wyjątku. To już jest złe, bo kto na Ziemi zna głębokość stosu, na której zostałaby wywołana ta metoda rzucania? Nawet jeśli wyłączysz generowanie śledzenia stosu i / lub buforujesz wyjątki, możesz tylko pozbyć się tej części kosztu wydajności.
Koszty rozwijania stosu zależą od tego, jakie mamy szczęście, przybliżając moduł obsługi wyjątków do skompilowanego kodu. Staranne ustrukturyzowanie kodu w celu uniknięcia wyszukiwania głębokich procedur obsługi wyjątków prawdopodobnie pomaga nam osiągnąć więcej szczęścia.
Jeśli wyeliminujemy oba efekty, koszt wydajności wyjątków jest kosztem lokalnego oddziału. Bez względu na to, jak pięknie to brzmi, nie oznacza to, że powinieneś używać wyjątków jako zwykłego przepływu sterowania, ponieważ w takim przypadku jesteś na łasce optymalizacji kompilatora! Powinieneś ich używać tylko w naprawdę wyjątkowych przypadkach, gdy częstotliwość wyjątków amortyzuje możliwy nieszczęśliwy koszt podniesienia faktycznego wyjątku.
Optymistyczna reguła wydaje się mieć częstotliwość 10 ^ 4, ponieważ wyjątki są wystarczająco wyjątkowe. To oczywiście zależy od dużej wagi samych wyjątków, dokładnych działań podjętych w procedurach obsługi wyjątków itp.
Rezultatem jest to, że gdy wyjątek nie zostanie zgłoszony, nie ponosisz kosztów, więc gdy wyjątkowy stan jest wystarczająco rzadki, obsługa wyjątków jest szybsza niż używanie za
if
każdym razem. Pełny post jest bardzo wart przeczytania.źródło
Moja odpowiedź jest niestety zbyt długa, aby opublikować tutaj. Pozwólcie, że streszczę tutaj i odsyłam do http://www.fuwjax.com/how-slow-are-java-exceptions/ celu uzyskania szczegółowych informacji.
Prawdziwe pytanie nie brzmi: „Jak wolno„ awarie są zgłaszane jako wyjątki ”w porównaniu z„ kodem, który nigdy nie zawodzi ”? jak może przyjąć zaakceptowana odpowiedź. Zamiast tego pytanie powinno brzmieć „Jak wolno„ awarie są zgłaszane jako wyjątki ”w porównaniu z awariami zgłaszanymi na inne sposoby?” Zasadniczo dwa inne sposoby zgłaszania awarii są albo z wartościami wartowników, albo z opakowaniami wyników.
Wartości Sentinel są próbą zwrócenia jednej klasy w przypadku sukcesu, a drugiej w przypadku niepowodzenia. Możesz myśleć o tym prawie jak o zwróceniu wyjątku zamiast rzucania go. Wymaga to współużytkowanej klasy nadrzędnej z obiektem sukcesu, a następnie wykonania sprawdzenia „instanceof” i kilku rzutów w celu uzyskania informacji o sukcesie lub niepowodzeniu.
Okazuje się, że przy zagrożeniu bezpieczeństwa typu wartości Sentinel są szybsze niż wyjątki, ale tylko około dwukrotnie. To może wydawać się dużo, ale to 2x pokrywa tylko koszt różnicy implementacji. W praktyce współczynnik ten jest znacznie niższy, ponieważ nasze metody, które mogą zawieść, są znacznie bardziej interesujące niż kilka operatorów arytmetycznych, jak w przykładowym kodzie w innym miejscu na tej stronie.
Z drugiej strony Owijarki Rezultatów wcale nie poświęcają bezpieczeństwa typu. Zawierają informacje o sukcesach i niepowodzeniach w jednej klasie. Dlatego zamiast „instanceof” udostępniają one „isSuccess ()” i funkcje pobierające zarówno obiekty sukcesu, jak i niepowodzenia. Jednak obiekty wynikowe są około dwa razy wolniejsze niż przy użyciu wyjątków. Okazuje się, że tworzenie nowego obiektu opakowującego za każdym razem jest znacznie droższe niż czasem rzucanie wyjątku.
Ponadto wyjątki stanowią podany język wskazujący, że metoda może zawieść. Nie ma innego sposobu, aby stwierdzić na podstawie samego interfejsu API, które metody będą zawsze (głównie) działały, a które powinny zgłaszać awarię.
Wyjątki są bezpieczniejsze niż wartowniki, szybsze niż obiekty wynikowe i mniej zaskakujące niż oba. Nie sugeruję, aby spróbować / catch zastąpić if / else, ale wyjątki to właściwy sposób zgłaszania awarii, nawet w logice biznesowej.
To powiedziawszy, chciałbym zauważyć, że dwa najczęstsze sposoby znacznego wpływu na wydajność, na które natknąłem się, to tworzenie niepotrzebnych obiektów i zagnieżdżonych pętli. Jeśli masz wybór między utworzeniem wyjątku lub nie utworzeniem wyjątku, nie twórz wyjątku. Jeśli masz wybór pomiędzy czasami tworzeniem wyjątku lub ciągłym tworzeniem innego obiektu, utwórz wyjątek.
źródło
Rozszerzyłem odpowiedzi udzielone przez @Mecki i @incarnate , bez wypełniania stosu śledzenia Java.
Z Javą 7+ możemy korzystać
Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)
. Ale w przypadku Java6 zobacz moją odpowiedź na to pytanieWyjście z Javą 1.6.0_45, na Core i7, 8 GB RAM:
Tak więc nadal metody zwracające wartości są szybsze w porównaniu do metod zgłaszających wyjątki. IMHO, nie możemy zaprojektować przejrzystego interfejsu API, używając tylko typów zwrotów zarówno dla przepływów sukcesu, jak i błędów. Metody generujące wyjątki bez stacktrace są 4-5 razy szybsze niż normalne wyjątki.
Edycja: NoStackTraceThrowable.java Dzięki @Greg
źródło
public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
With Java 7+, we can use
ale później pisałeś ,Output with Java 1.6.0_45,
więc jest to wynik Java 6 lub 7?Throwable
konstruktora, który maboolean writableStackTrace
arg. Ale nie jest to obecne w Javie 6 i niższych. Dlatego podałem niestandardową implementację dla Java 6 i niższych. Powyższy kod dotyczy Java 6 i niższych. Przeczytaj uważnie pierwszą linię drugiego akapitu.Optional
lub podobny przyszedł nieco później do Java. Wcześniej używaliśmy również obiektów otoki z flagami powodzenia / błędów. Ale wydaje się to trochę hacków i nie wydaje mi się naturalne.Jakiś czas temu napisałem klasę, aby przetestować względną wydajność konwersji ciągów znaków na ints przy użyciu dwóch metod: (1) wywołaj Integer.parseInt () i złap wyjątek lub (2) dopasuj ciąg z wyrażeniem regularnym i wywołaj parseInt () tylko jeśli mecz się powiedzie. Użyłem wyrażenia regularnego w najbardziej efektywny sposób, w jaki mogłem (tj. Tworząc obiekty Pattern i Matcher przed interferencją w pętli) i nie wydrukowałem ani nie zapisałem śladów stosu z wyjątków.
W przypadku listy dziesięciu tysięcy ciągów, jeśli wszystkie były poprawnymi liczbami, podejście parseInt () było czterokrotnie szybsze niż podejście wyrażenia regularnego. Ale jeśli tylko 80% łańcuchów jest poprawnych, regex był dwa razy szybszy niż parseInt (). A jeśli 20% było poprawnych, co oznacza, że wyjątek został zgłoszony i wyłapany w 80% przypadków, regex był około dwadzieścia razy szybszy niż parseInt ().
Byłem zaskoczony wynikiem, biorąc pod uwagę, że podejście wyrażenia regularnego przetwarza poprawne ciągi dwa razy: raz dla dopasowania i ponownie dla parseInt (). Ale rzucanie i łapanie wyjątków z nawiązką to zrekompensowało. Taka sytuacja prawdopodobnie nie zdarza się bardzo często w prawdziwym świecie, ale jeśli tak, zdecydowanie nie należy stosować techniki wychwytywania wyjątków. Ale jeśli weryfikujesz tylko dane wejściowe użytkownika lub coś w tym rodzaju, z całą pewnością skorzystaj z metody parseInt ().
źródło
Integer.ParseInt()
) i spodziewam się, że w większości przypadków dane wejściowe użytkownika będą poprawne, więc w moim przypadku użycia wydaje się, że od czasu do czasu trafienie wyjątkowego trafienia jest właściwą drogą .Myślę, że pierwszy artykuł odnosi się do przemierzania stosu wywołań i tworzenia śladu stosu jako drogiej części, a podczas gdy drugi artykuł tego nie mówi, myślę, że jest to najdroższa część tworzenia obiektów. John Rose ma artykuł, w którym opisuje różne techniki przyspieszania wyjątków . (Wstępne przydzielanie i ponowne użycie wyjątku, wyjątki bez śladów stosu itp.)
Ale nadal - uważam, że należy to uznać za zło konieczne, w ostateczności. Powodem tego jest emulacja funkcji w innych językach, które nie są (jeszcze) dostępne w JVM. NIE powinieneś przyzwyczajać się do używania wyjątków dla przepływu kontrolnego. Zwłaszcza nie ze względu na wydajność! Jak sam wspominasz w punkcie 2, ryzykujesz w ten sposób maskowanie poważnych błędów w kodzie i trudniej będzie go utrzymać nowym programistom.
Znaki mikrodruku w Javie są zaskakująco trudne do poprawienia (jak mi powiedziano), zwłaszcza gdy wchodzisz na terytorium JIT, więc naprawdę wątpię, aby stosowanie wyjątków było szybsze niż „powrót” w prawdziwym życiu. Na przykład podejrzewam, że masz w swoim teście od 2 do 5 ramek stosu? Teraz wyobraź sobie, że Twój kod zostanie wywołany przez komponent JSF wdrożony przez JBoss. Teraz możesz mieć ślad stosu, który ma kilka stron.
Być może mógłbyś opublikować swój kod testowy?
źródło
Nie wiem, czy te tematy się odnoszą, ale kiedyś chciałem zastosować jedną sztuczkę polegającą na śledzeniu stosu bieżącego wątku: chciałem odkryć nazwę metody, która wyzwoliła tworzenie instancji wewnątrz instancji klasy (tak, pomysł jest szalony, Całkowicie się poddałem). Odkryłem więc, że wywoływanie
Thread.currentThread().getStackTrace()
jest bardzo wolne (dzięki natywnejdumpThreads
metodzie, której używa wewnętrznie).Zatem Java
Throwable
ma odpowiednio natywną metodęfillInStackTrace
. Myślę, żecatch
opisany wcześniej blok- zabójca w jakiś sposób wyzwala wykonanie tej metody.Ale pozwól, że opowiem ci inną historię ...
W Scali niektóre funkcje funkcjonalne są kompilowane w JVM przy użyciu
ControlThrowable
, który rozszerzaThrowable
i zastępuje jegofillInStackTrace
w następujący sposób:Zaadaptowałem więc powyższy test (ilość cykli zmniejsza się o dziesięć, moja maszyna jest nieco wolniejsza :):
Tak więc wyniki są następujące:
Widać, jedyną różnicą pomiędzy
method3
imethod4
jest to, że oni rzucać różne rodzaje wyjątków. Tak,method4
nadal jest wolniejszy niżmethod1
imethod2
, ale różnica jest o wiele bardziej akceptowalna.źródło
Zrobiłem kilka testów wydajności z JVM 1.5 i stosowanie wyjątków było co najmniej 2x wolniejsze. Średnio: czas wykonania na trywialnie małej metodzie ponad trzykrotnie (3x) z wyjątkami. W trywialnie małej pętli, która musiała wychwycić wyjątek, zaobserwowano dwukrotny wzrost czasu pracy.
Widziałem podobne liczby w kodzie produkcyjnym, a także w testach mikro.
Wyjątki powinny zdecydowanie NIEstosować do czegokolwiek, co często się nazywa. Zgłaszanie tysięcy wyjątków na sekundę spowodowałoby ogromną szyjkę butelki.
Na przykład użycie „Integer.ParseInt (...)” do znalezienia wszystkich złych wartości w bardzo dużym pliku tekstowym - bardzo zły pomysł. (Widziałem, jak ta metoda narzędzia zabija wydajność kodu produkcyjnego)
Używanie wyjątku do zgłaszania złej wartości w formularzu GUI użytkownika, prawdopodobnie nie jest tak źle z punktu widzenia wydajności.
Niezależnie od tego, czy jest to dobra praktyka projektowa, wybrałbym zasadę: jeśli błąd jest normalny / oczekiwany, użyj wartości zwracanej. Jeśli jest nienormalny, użyj wyjątku. Na przykład: odczyt danych wejściowych użytkownika, nieprawidłowe wartości są normalne - użyj kodu błędu. Przekazując wartość do wewnętrznej funkcji narzędzia, niepoprawne wartości powinny być filtrowane przez wywołanie kodu - użyj wyjątku.
źródło
Wyjątkowa wydajność w Javie i C # pozostawia wiele do życzenia.
Jako programiści zmusza nas to do przestrzegania zasady „wyjątki powinny być powodowane rzadko”, po prostu ze względów praktycznych.
Jednak jako informatycy powinniśmy zbuntować się przeciwko temu problematycznemu stanowi. Osoba tworząca funkcję często nie ma pojęcia, jak często będzie ona wywoływana ani czy prawdopodobieństwo sukcesu lub niepowodzenia jest bardziej prawdopodobne. Tylko rozmówca ma te informacje. Próba uniknięcia wyjątków prowadzi do niejasności interfejsów API, w których w niektórych przypadkach mamy tylko czyste, ale powolne wersje wyjątków, aw innych przypadkach mamy szybkie, ale niezgrabne błędy wartości zwrotnej, aw jeszcze innych przypadkach dochodzimy do obu . Implementator biblioteki może być zmuszony napisać i utrzymywać dwie wersje interfejsów API, a osoba dzwoniąca musi zdecydować, która z dwóch wersji ma być używana w każdej sytuacji.
To rodzaj bałaganu. Gdyby wyjątki miały lepszą wydajność, moglibyśmy uniknąć tych nieporadnych idiomów i zastosować wyjątki, ponieważ miały one być użyte ... jako usystematyzowana funkcja zwracania błędów.
Naprawdę chciałbym zobaczyć mechanizmy wyjątków wdrażane przy użyciu technik bliższych wartościom zwracanym, abyśmy mogli mieć wydajność bliższą zwracanym wartościom, ponieważ do tego wracamy w kodzie wrażliwym na wydajność.
Oto przykładowy kod, który porównuje wydajność wyjątku do wydajności zwracającej błąd.
TestIt klasy publicznej {
}
A oto wyniki:
Sprawdzanie i propagowanie wartości zwracanych dodaje pewien koszt w porównaniu do wywołania zerowego linii bazowej, a koszt ten jest proporcjonalny do głębokości wywołania. Przy głębokości łańcucha połączeń wynoszącej 8 wersja sprawdzająca wartość zwracaną po błędzie była około 27% wolniejsza niż wersja podstawowa, która nie sprawdzała zwracanych wartości.
Dla porównania, działanie wyjątku nie jest funkcją głębokości wywołania, ale częstotliwości wyjątku. Jednak degradacja wraz ze wzrostem częstotliwości wyjątków jest znacznie bardziej dramatyczna. Przy tylko 25% częstotliwości błędów kod działał 24-TIMES wolniej. Przy częstotliwości błędu 100% wersja wyjątku jest prawie 100-TIMES wolniejsza.
To sugeruje mi, że być może dokonujemy niewłaściwych kompromisów w naszych implementacjach wyjątków. Wyjątki mogą być szybsze, albo poprzez unikanie kosztownych spacerów łodygami, albo przez bezpośrednie przekształcenie ich w sprawdzanie wartości zwracanej przez kompilator. Dopóki tego nie zrobią, będziemy ich unikać, gdy chcemy, aby nasz kod działał szybko.
źródło
HotSpot jest w stanie usunąć kod wyjątku dla wyjątków generowanych przez system, pod warunkiem, że wszystkie są wstawiane. Jednak jawnie utworzony wyjątek i te, które w przeciwnym razie nie zostały usunięte, spędzają dużo czasu na tworzeniu śladu stosu. Zastąp,
fillInStackTrace
aby zobaczyć, jak może to wpłynąć na wydajność.źródło
Nawet jeśli zgłaszanie wyjątku nie jest powolne, nadal złym pomysłem jest zgłaszanie wyjątków dla normalnego przebiegu programu. Używany w ten sposób jest analogiczny do GOTO ...
Wydaje mi się, że to jednak nie odpowiada na pytanie. Wyobrażam sobie, że „konwencjonalna” mądrość powoływania wyjątków na powolne była prawdziwa we wcześniejszych wersjach Java (<1.4). Utworzenie wyjątku wymaga, aby maszyna wirtualna utworzyła cały ślad stosu. Od tego czasu wiele się zmieniło w maszynie wirtualnej, aby przyspieszyć i prawdopodobnie jest to jeden z obszarów, który został ulepszony.
źródło
break
lubreturn
, niegoto
.Po prostu porównajmy powiedzmy Integer.parseInt z następującą metodą, która zwraca wartość domyślną w przypadku danych niemożliwych do przeanalizowania zamiast zgłaszania wyjątku:
Tak długo, jak obie metody będą stosowane do „poprawnych” danych, obie będą działać w przybliżeniu z tą samą szybkością (nawet jeśli Integer.parseInt radzi sobie z bardziej złożonymi danymi). Ale gdy tylko spróbujesz parsować nieprawidłowe dane (np. Parsować „abc” 1.000.000 razy), różnica w wydajności powinna być niezbędna.
źródło
Świetny post na temat wydajności wyjątków to:
https://shipilev.net/blog/2014/exceptional-performance/
Tworzenie instancji a ponowne wykorzystywanie istniejących, ze śledzeniem stosu i bez, itp .:
W zależności od głębokości śledzenia stosu:
Aby uzyskać więcej informacji (w tym asembler x64 z JIT) przeczytaj oryginalny post na blogu.
Oznacza to, że Hibernacja / Wiosna / etc-EE-shit są powolne z powodu wyjątków (xD) i przepisywanie kontroli aplikacji odpływa od wyjątków (zamień ją na
continure
/break
i zwracanieboolean
flag jak w C z wywołania metody) poprawiają wydajność aplikacji 10x-100x , w zależności od tego, jak często je rzucasz))źródło
Zmieniłem odpowiedź @Mecki powyżej, aby metoda1 zwracała wartość logiczną i sprawdzała metodę wywołującą, ponieważ nie można po prostu zastąpić wyjątku niczym. Po dwóch cyklach metoda1 była nadal najszybsza lub tak szybka jak metoda2.
Oto migawka kodu:
i wyniki:
Uruchom 1
Uruchom 2
źródło
Wyjątkiem jest przeznaczona do obsługi nieoczekiwanych warunków w czasie wykonywania tylko.
Użycie wyjątku zamiast prostej walidacji, które można wykonać w czasie kompilacji, opóźni walidację do czasu wykonania. To z kolei zmniejszy wydajność programu.
Zgłoszenie wyjątku zamiast prostego sprawdzania poprawności if..else sprawi, że kod będzie trudniejszy do napisania i utrzymania.
źródło
Moja opinia o prędkości wyjątku a programowym sprawdzaniu danych.
Wiele klas miało konwerter wartości na ciąg (skaner / parser), szanowane i dobrze znane biblioteki;)
zwykle ma formę
nazwa wyjątku jest tylko przykładem, zwykle nie jest zaznaczona (środowisko wykonawcze), więc deklaracja wyrzuca jest tylko moim obrazem
czasami istnieją druga forma:
nigdy nie rzucając
Kiedy drugi nie jest dostępny (lub programista czyta za mało dokumentów i używa tylko pierwszego), napisz taki kod z wyrażeniem regularnym. Wyrażenia regularne są fajne, poprawne politycznie itp .:
z tym kodem programiści nie kosztują wyjątków. ALE MA porównywalny WYSOKIE koszty wyrażeń regularnych ZAWSZE w porównaniu z niewielkimi kosztami wyjątków.
Używam prawie zawsze w takim kontekście
bez analizowania stacktrace itp., wierzę, że po twoich wykładach dość szybko.
Nie bój się Wyjątków
źródło
Dlaczego wyjątki powinny być wolniejsze niż normalne zwroty?
Dopóki nie wydrukujesz stosu śledzenia na terminalu, nie zapiszesz go w pliku lub czymś podobnym, catch-block nie wykona więcej pracy niż inne bloki kodu. Nie mogę więc sobie wyobrazić, dlaczego „rzucanie nową my_cool_error ()” powinno być tak wolne.
Dobre pytanie i czekam na dalsze informacje na ten temat!
źródło