Nie chcę dyskusji o tym, kiedy i nie należy rzucać wyjątków. Chcę rozwiązać prosty problem. W 99% przypadków argument za niewyrzucaniem wyjątków obraca się wokół ich powolności, podczas gdy druga strona twierdzi (za pomocą testu porównawczego), że prędkość nie jest problemem. Przeczytałem wiele blogów, artykułów i postów dotyczących jednej lub drugiej strony. Więc o co chodzi?
c#
.net
performance
exception
Goran
źródło
źródło
Odpowiedzi:
Jestem po stronie „nie wolno” - a dokładniej „nie na tyle wolno, by warto było ich unikać przy normalnym użytkowaniu”. Napisałem na ten temat dwa krótkie artykuły . Istnieje krytyka aspekcie odniesienia, które są głównie w dół do „w prawdziwym życiu nie byłoby bardziej stos przejść, więc chcesz dmuchać cache etc” - ale za pomocą kodów błędów na swój sposób pracy stosu byłoby również wysadzić pamięć podręczną, więc nie uważam tego za szczególnie dobry argument.
Żeby było jasne - nie popieram używania wyjątków, jeśli nie są one logiczne. Na przykład
int.TryParse
jest całkowicie odpowiedni do konwersji danych od użytkownika. Jest to odpowiednie podczas odczytywania pliku wygenerowanego maszynowo, gdzie błąd oznacza „Plik nie jest w formacie, w jakim ma być, naprawdę nie chcę tego robić, ponieważ nie wiem, co jeszcze może być nie tak. "Korzystając z wyjątków w „tylko rozsądnych okolicznościach”, nigdy nie widziałem aplikacji, której wydajność byłaby znacznie ograniczona przez wyjątki. Zasadniczo wyjątki nie powinny zdarzać się często, chyba że masz poważne problemy z poprawnością, a jeśli masz znaczące problemy z poprawnością, wydajność nie jest największym problemem, z którym się spotykasz.
źródło
Ostateczną odpowiedź na to pytanie udzielił facet, który je wdrożył - Chris Brumme. Napisał na ten temat świetny artykuł na blogu (uwaga - jest bardzo długi) (ostrzeżenie2 - bardzo dobrze napisany, jeśli jesteś technikiem, przeczytasz go do końca, a potem będziesz musiał nadrobić godziny po pracy :) )
Streszczenie: są powolne. Są zaimplementowane jako wyjątki Win32 SEH, więc niektóre nawet przekraczają granicę procesora ring 0! Oczywiście w prawdziwym świecie będziesz wykonywać wiele innych czynności, więc dziwny wyjątek w ogóle nie zostanie zauważony, ale jeśli użyjesz ich do przepływu programu, spodziewaj się, że Twoja aplikacja zostanie wbita. To kolejny przykład marketingowej maszyny MS wyrządzającej nam krzywdę. Przypominam sobie, że jeden Microsoft opowiadał nam, jak ponieśli absolutnie zerowe koszty ogólne, co jest kompletne.
Chris podaje trafny cytat:
źródło
Nie mam pojęcia, o czym ludzie mówią, kiedy mówią, że są powolni tylko wtedy, gdy są rzucani.
EDYCJA: Jeśli wyjątki nie są zgłaszane, oznacza to, że robisz nowy wyjątek () lub coś w tym stylu. W przeciwnym razie wyjątek spowoduje zawieszenie wątku i przejście stosu. Może to być w porządku w mniejszych sytuacjach, ale w witrynach o dużym ruchu poleganie na wyjątkach jako przepływie pracy lub mechanizmie ścieżki wykonania z pewnością spowoduje problemy z wydajnością. Wyjątki same w sobie nie są złe i są przydatne do wyrażania wyjątkowych warunków
Przepływ pracy wyjątków w aplikacji .NET korzysta z wyjątków pierwszej i drugiej szansy. W przypadku wszystkich wyjątków, nawet jeśli je przechwytujesz i obsługujesz, obiekt wyjątku jest nadal tworzony, a struktura nadal musi przejść stos, aby znaleźć procedurę obsługi. Jeśli złapiesz i wyrzucisz ponownie, oczywiście zajmie to więcej czasu - dostaniesz wyjątek pierwszej szansy, złapiesz go, wyrzucisz ponownie, powodując kolejny wyjątek pierwszej szansy, który następnie nie znajdzie obsługi, co spowoduje wyjątek drugiej szansy.
Wyjątki to także obiekty na stercie - więc jeśli wyrzucasz mnóstwo wyjątków, powoduje to zarówno problemy z wydajnością, jak i pamięcią.
Ponadto, zgodnie z moją kopią „Testowania wydajności aplikacji internetowych Microsoft .NET” napisaną przez zespół ACE:
„Obsługa wyjątków jest kosztowna. Wykonywanie zaangażowanego wątku jest zawieszane, podczas gdy CLR powtarza się przez stos wywołań w poszukiwaniu właściwej procedury obsługi wyjątków, a kiedy zostanie znaleziony, procedura obsługi wyjątku i pewna liczba końcowych bloków muszą mieć szansę na wykonanie zanim będzie można przeprowadzić regularne przetwarzanie ”.
Moje własne doświadczenie w tej dziedzinie pokazało, że redukcja wyjątków znacząco poprawiła wydajność. Oczywiście są inne rzeczy, które bierzesz pod uwagę podczas testowania wydajności - na przykład, jeśli twoje operacje wejścia / wyjścia dysku są przerywane lub twoje zapytania są w sekundach, to powinno być twoim celem. Jednak znajdowanie i usuwanie wyjątków powinno być istotną częścią tej strategii.
źródło
Jak rozumiem, argumentem nie jest to, że rzucanie wyjątków jest złe, ale samo w sobie jest powolne. Zamiast tego chodzi o użycie konstrukcji throw / catch jako pierwszej klasy sposobu kontrolowania normalnej logiki aplikacji, zamiast bardziej tradycyjnych konstrukcji warunkowych.
Często w normalnej logice aplikacji wykonujesz pętle, w których ta sama akcja jest powtarzana tysiące / miliony razy. W tym przypadku, przy bardzo prostym profilowaniu (patrz klasa Stopwatch), możesz sam przekonać się, że rzucanie wyjątku zamiast powiedzieć proste polecenie if może okazać się znacznie wolniejsze.
W rzeczywistości kiedyś przeczytałem, że zespół .NET w firmie Microsoft wprowadził metody TryXXXXX w .NET 2.0 do wielu podstawowych typów FCL, szczególnie dlatego, że klienci narzekali, że działanie ich aplikacji jest tak wolne.
Okazuje się, że w wielu przypadkach było to spowodowane próbą konwersji typu przez klientów w pętli, a każda próba zakończyła się niepowodzeniem. Wyjątek konwersji został zgłoszony, a następnie przechwycony przez program obsługi wyjątków, który następnie połknął wyjątek i kontynuował pętlę.
Firma Microsoft zaleca teraz, aby metody TryXXX były używane szczególnie w tej sytuacji, aby uniknąć takich możliwych problemów z wydajnością.
Mogę się mylić, ale wygląda na to, że nie jesteś pewien prawdziwości „testów porównawczych”, o których czytałeś. Proste rozwiązanie: wypróbuj to sam.
źródło
Mój serwer XMPP zyskał znaczne przyspieszenie (przepraszam, brak rzeczywistych liczb, czysto obserwacyjne) po tym, jak konsekwentnie próbowałem im zapobiec (na przykład sprawdzałem, czy gniazdo jest podłączone, zanim spróbujesz odczytać więcej danych) i dałem sobie sposoby ich uniknięcia (wspomniane metody TryX). To było tylko z około 50 aktywnymi (czatującymi) wirtualnymi użytkownikami.
źródło
Aby dodać moje własne niedawne doświadczenia do tej dyskusji: zgodnie z większością tego, co napisano powyżej, stwierdziłem, że rzucanie wyjątków jest niezwykle powolne, gdy jest wykonywane wielokrotnie, nawet bez uruchomionego debugera. Po prostu zwiększyłem wydajność dużego programu, który piszę, o 60%, zmieniając około pięciu wierszy kodu: przełączając się na model kodu powrotnego zamiast rzucać wyjątki. To prawda, że kod, o którym mowa, działał tysiące razy i potencjalnie rzucał tysiące wyjątków, zanim go zmieniłem. Zgadzam się więc z powyższym stwierdzeniem: wyrzucaj wyjątki, gdy coś ważnego faktycznie pójdzie nie tak, a nie jako sposób kontrolowania przepływu aplikacji w "oczekiwanych" sytuacjach.
źródło
Jeśli porównasz je z kodami zwrotnymi, są powolne jak diabli. Jednak, jak wskazywali poprzednie postery, nie chcesz wrzucać do normalnego działania programu, więc osiągasz perfekcję tylko wtedy, gdy pojawia się problem, aw większości przypadków wydajność nie ma już znaczenia (ponieważ wyjątek i tak oznacza blokadę drogi).
Zdecydowanie warto ich używać zamiast kodów błędów, zalety to ogromna IMO.
źródło
Nigdy nie miałem żadnych problemów z wydajnością z wyjątkami. Często korzystam z wyjątków - nigdy nie używam kodów zwrotnych, jeśli mogę. Są złą praktyką i moim zdaniem pachną jak kod spaghetti.
Myślę, że wszystko sprowadza się do tego, jak używasz wyjątków: jeśli używasz ich jak kodów powrotu (każde wywołanie metody w stosie łapie i ponownie rzuca), to tak, będą one powolne, ponieważ masz nad głową każdy pojedynczy złapanie / rzut.
Ale jeśli rzucisz na dół stosu i złapiesz na górze (zastąpisz cały łańcuch kodów powrotu jednym rzutem / złapaniem), wszystkie kosztowne operacje są wykonywane raz.
Pod koniec dnia są one ważną funkcją językową.
Żeby udowodnić mój punkt widzenia
Uruchom kod pod tym linkiem (zbyt duży, aby uzyskać odpowiedź).
Wyniki na moim komputerze:
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
Znaczniki czasu są wyprowadzane na początku, między kodami powrotu a wyjątkami, na końcu. W obu przypadkach zajmuje to tyle samo czasu. Pamiętaj, że musisz skompilować się z optymalizacjami.
źródło
Ale mono zgłasza wyjątek 10x szybciej niż tryb autonomiczny .net, a tryb autonomiczny .net zgłasza wyjątek 60x szybciej niż tryb debuggera .net. (Maszyny testujące mają ten sam model procesora)
źródło
W trybie zwolnienia narzut jest minimalny.
O ile nie zamierzasz używać wyjątków do sterowania przepływem (na przykład nielokalnych wyjść) w sposób rekurencyjny, wątpię, czy będziesz w stanie zauważyć różnicę.
źródło
W środowisku CLR systemu Windows dla łańcucha wywołań o głębokości 8 zgłoszenie wyjątku jest 750 razy wolniejsze niż sprawdzanie i propagowanie wartości zwracanej. (patrz poniżej testy porównawcze)
Ten wysoki koszt wyjątków wynika z faktu, że środowisko CLR systemu Windows integruje się z czymś, co nazywa się obsługą wyjątków strukturalnych systemu Windows . Dzięki temu wyjątki mogą być prawidłowo przechwytywane i zgłaszane w różnych środowiskach wykonawczych i językach. Jednak jest to bardzo powolne.
Wyjątki w środowisku wykonawczym Mono (na dowolnej platformie) są znacznie szybsze, ponieważ nie integruje się z SEH. Jednak podczas przekazywania wyjątków w wielu środowiskach wykonawczych występuje utrata funkcjonalności, ponieważ nie używa niczego takiego jak SEH.
Oto skrócone wyniki z mojego testu porównawczego wyjątków i zwracanych wartości dla środowiska CLR systemu Windows.
A oto kod ...
źródło
Jedna krótka uwaga na temat wydajności związanej z wyłapywaniem wyjątków.
Kiedy ścieżka wykonania wchodzi w blok „try”, nic magicznego się nie dzieje. Nie ma instrukcji „try” i żadnych kosztów związanych z wejściem lub wyjściem z bloku try. Informacje o bloku try są przechowywane w metadanych metody, a te metadane są używane w czasie wykonywania za każdym razem, gdy zostanie zgłoszony wyjątek. Silnik wykonawczy przechodzi w dół stosu, szukając pierwszego wywołania zawartego w bloku try. Wszelkie narzuty związane z obsługą wyjątków występują tylko wtedy, gdy są zgłaszane wyjątki.
źródło
Podczas pisania klas / funkcji do wykorzystania przez innych wydaje się, że trudno powiedzieć, kiedy wyjątki są odpowiednie. Jest kilka przydatnych części BCL, z których musiałem zrezygnować i wybrać pinvoke, ponieważ rzucają wyjątki zamiast zwracać błędy. W niektórych przypadkach można to obejść, ale w innych, takich jak System.Management i Performance Counters, istnieją zastosowania, w których trzeba wykonywać pętle, w których wyjątki są często generowane przez BCL.
Jeśli piszesz bibliotekę i istnieje niewielkie prawdopodobieństwo, że Twoja funkcja może być używana w pętli i istnieje możliwość dużej liczby iteracji, użyj wzorca Try .. lub innego sposobu, aby ujawnić błędy obok wyjątków. I nawet wtedy trudno powiedzieć, jak bardzo Twoja funkcja zostanie wywołana, jeśli jest używana przez wiele procesów we wspólnym środowisku.
W moim własnym kodzie wyjątki są generowane tylko wtedy, gdy sytuacja jest tak wyjątkowa, że trzeba spojrzeć na ślad stosu i zobaczyć, co poszło nie tak, a następnie to naprawić. Więc prawie ponownie napisałem części BCL, aby używać obsługi błędów w oparciu o wzorzec Try .. zamiast wyjątków.
źródło