Muszę przyznać, że zwykle nie kłopotałem się przełączaniem między konfiguracjami debugowania i wydania w moim programie i zwykle decydowałem się na konfigurację debugowania , nawet jeśli programy są faktycznie wdrażane u klientów.
O ile mi wiadomo, jedyną różnicą między tymi konfiguracjami, jeśli nie zmienisz go ręcznie, jest to, że Debug ma DEBUG
zdefiniowaną stałą, a Release ma zaznaczony kod Optimize .
Tak więc moje pytania są dwojakie:
Czy występują duże różnice wydajności między tymi dwiema konfiguracjami. Czy jest jakiś konkretny rodzaj kodu, który spowoduje tutaj duże różnice w wydajności, czy faktycznie nie jest to tak ważne?
Czy istnieje jakiś rodzaj kodu, który będzie działał poprawnie w konfiguracji debugowania, który może się nie powieść w konfiguracji wydania , czy możesz być pewien, że kod, który jest testowany i działa poprawnie w konfiguracji debugowania, będzie również działał dobrze w konfiguracji wydania.
źródło
Odpowiedzi:
Sam kompilator C # nie zmienia zbyt wiele emitowanej IL w kompilacji Release. Godne uwagi jest to, że nie emituje już kodów NOP, które pozwalają ustawić punkt przerwania na nawiasach klamrowych. Duży to optymalizator wbudowany w kompilator JIT. Wiem, że dokonuje następujących optymalizacji:
Metoda inliningu. Wywołanie metody jest zastępowane przez wstrzyknięcie kodu metody. Jest to duży, sprawia, że dostęp do nieruchomości jest zasadniczo bezpłatny.
Przydział rejestru procesora. Lokalne zmienne i argumenty metody mogą pozostać przechowywane w rejestrze procesora bez (lub rzadziej) przechowywania z powrotem do ramki stosu. Jest to duży problem, szczególnie utrudniający debugowanie zoptymalizowanego kodu. I nadając zmiennemu słowu kluczowemu znaczenie.
Eliminacja sprawdzania indeksu tablicy. Ważna optymalizacja podczas pracy z tablicami (wszystkie klasy kolekcji .NET używają tablicy wewnętrznie). Gdy kompilator JIT może sprawdzić, czy pętla nigdy nie indeksuje tablicy poza granicami, eliminuje to sprawdzanie indeksu. Duży.
Rozwijanie pętli. Pętle z małymi ciałami są ulepszane przez powtarzanie kodu do 4 razy w ciele i mniej zapętlania. Zmniejsza koszt oddziału i poprawia super-skalarne opcje wykonania procesora.
Eliminacja martwego kodu. Instrukcja taka jak if (false) {/ ... /} zostanie całkowicie wyeliminowana. Może się to zdarzyć z powodu ciągłego składania i wkładania. W innych przypadkach kompilator JIT może ustalić, że kod nie ma możliwego efektu ubocznego. Ta optymalizacja sprawia, że profilowanie kodu jest tak trudne.
Podnoszenie kodu. Kod wewnątrz pętli, na który pętla nie ma wpływu, można przenieść z pętli. Optymalizator kompilatora C poświęci znacznie więcej czasu na znalezienie okazji do podniesienia. Jest to jednak kosztowna optymalizacja ze względu na wymaganą analizę przepływu danych, a jitter nie może sobie pozwolić na czas, więc podnoszą tylko oczywiste przypadki. Zmuszanie programistów .NET do pisania lepszego kodu źródłowego i podnoszenia się.
Wspólna eliminacja podwyrażeń. x = y + 4; z = y + 4; staje się z = x; Dość powszechne w instrukcjach takich jak dest [ix + 1] = src [ix + 1]; napisane dla czytelności bez wprowadzania zmiennej pomocniczej. Nie trzeba zmniejszać czytelności.
Stałe składanie. x = 1 + 2; staje się x = 3; Ten prosty przykład został wcześnie wychwycony przez kompilator, ale dzieje się to w czasie JIT, kiedy inne optymalizacje umożliwiają to.
Kopiuj propagację. x = a; y = x; staje się y = a; Pomaga to alokatorowi rejestru podejmować lepsze decyzje. W jitteru x86 jest to wielka sprawa, ponieważ ma niewiele rejestrów do pracy. Wybór odpowiednich jest kluczowy dla perf.
Są to bardzo ważne optymalizacje, które mogą spowodować wielki kontrakt różnicy kiedy, na przykład, profil kompilacji Debug aplikacji i porównać go do kompilacji Release. To naprawdę ma znaczenie tylko wtedy, gdy kod znajduje się na twojej krytycznej ścieżce, 5 do 10% pisanego kodu, które faktycznie wpływa na perf twojego programu. Optymalizator JIT nie jest wystarczająco inteligentny, aby z góry wiedzieć, co jest najważniejsze, może zastosować tylko pokrętło „zamień na jedenaście” dla całego kodu.
Na efektywny wynik tych optymalizacji czasu wykonywania programu często wpływa kod działający w innym miejscu. Odczytywanie pliku, wykonywanie zapytania dbase itp. Optymalizacja JIT czyni pracę całkowicie niewidoczną. Nie przeszkadza to jednak :)
Optymalizator JIT jest dość niezawodnym kodem, głównie dlatego, że został przetestowany miliony razy. Bardzo rzadko występują problemy z wersją kompilacji wydania programu. Tak się jednak zdarza. Zarówno jitter x64, jak i x86 miały problemy ze strukturami. Jitter x86 ma problemy ze spójnością zmiennoprzecinkową, powodując nieznacznie różne wyniki, gdy półprodukty obliczeń zmiennoprzecinkowych są przechowywane w rejestrze FPU z 80-bitową precyzją, zamiast zostać obciętym po opróżnieniu do pamięci.
źródło
LinkedList<T>
nie, chociaż nie są używane zbyt często.volatile
kluczowe nie dotyczy zmiennych lokalnych przechowywanych w ramce stosu. Z dokumentacji na msdn.microsoft.com/en-us/library/x13ttww7.aspx : „Zmienne słowo kluczowe można zastosować tylko do pól klasy lub struktury. Zmiennych lokalnych nie można uznać za zmienne”.Debug
iRelease
buduje pod tym względem, to pole wyboru „optymalizuj kod”, które jest zwykle włączone,Release
ale wyłączoneDebug
. Tylko po to, aby czytelnicy nie zaczęli myśleć, że istnieją „magiczne”, niewidoczne różnice między dwiema konfiguracjami kompilacji, które wykraczają poza to, co można znaleźć na stronie właściwości projektu w Visual Studio.Tak, istnieje wiele różnic wydajności, które naprawdę dotyczą całego kodu. Debugowanie bardzo mało optymalizuje wydajność, a tryb zwolnienia bardzo;
Tylko kod oparty na
DEBUG
stałej może działać inaczej z kompilacją wydania. Poza tym nie powinieneś widzieć żadnych problemów.Przykładem kodu frameworka zależnego od
DEBUG
stałej jestDebug.Assert()
metoda z[Conditional("DEBUG)"]
zdefiniowanym atrybutem . Oznacza to, że zależy to również odDEBUG
stałej i nie jest to uwzględnione w kompilacji wydania.źródło
DEBUG
:AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length))
.asp.net
i używasz debugowania zamiast wydania, na stronie mogą zostać dodane skrypty, takie jak:MicrosoftAjax.debug.js
zawierający około 7 tys. Linii.Zależy to w dużej mierze od charakteru twojej aplikacji. Jeśli twoja aplikacja jest obciążona interfejsem użytkownika, prawdopodobnie nie zauważysz żadnej różnicy, ponieważ użytkownik jest najwolniejszym komponentem podłączonym do nowoczesnego komputera. Jeśli korzystasz z niektórych animacji interfejsu użytkownika, możesz sprawdzić, czy możesz zauważyć zauważalne opóźnienie podczas uruchamiania w kompilacji DEBUG.
Jeśli jednak masz wiele obliczeń obciążających obliczenia, zauważysz różnice (może wynosić nawet 40%, jak wspomniano w @Pieter, choć będzie to zależało od charakteru obliczeń).
Zasadniczo jest to kompromis projektowy. Jeśli wydajesz w wersji DEBUG, to jeśli użytkownicy napotkają problemy, możesz uzyskać bardziej znaczące śledzenie i możesz wykonać bardziej elastyczną diagnostykę. Wydając w wersji DEBUG, unikasz również optymalizatora produkującego niejasne Heisenbugs .
źródło
Z mojego doświadczenia wynika, że średnie lub większe aplikacje są zauważalnie bardziej responsywne w wersji Release. Wypróbuj aplikację i przekonaj się, jak to jest.
Jedną z rzeczy, które mogą cię ugryźć w kompilacjach wersji, jest to, że kod kompilacji debugowania może czasami tłumić warunki wyścigu i inne błędy związane z wątkami. Zoptymalizowany kod może spowodować zmianę kolejności instrukcji, a szybsze wykonanie może zaostrzyć niektóre warunki wyścigu.
źródło
Nigdy nie należy wypuszczać wersji .NET Debug do wersji produkcyjnej. Może zawierać brzydki kod do obsługi Edycji i Kontynuacji lub kto wie co jeszcze. O ile mi wiadomo, dzieje się tak tylko w VB, a nie w C # (uwaga: oryginalny post jest oznaczony jako C #) , ale nadal powinien dawać powód do wstrzymania się z tym, co Microsoft uważa, że mogą robić z wersją debugowania. W rzeczywistości przed wersją .NET 4.0 kod VB przecieka pamięć proporcjonalnie do liczby instancji obiektów ze zdarzeniami, które konstruujesz w celu obsługi edycji i kontynuowania. (Chociaż zgłoszono, że jest to naprawione według https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , wygenerowany kod wygląda paskudnie, tworząc
WeakReference
obiekty i dodając je do statycznej listyz blokadą) Z pewnością nie chcę tego rodzaju wsparcia debugowania w środowisku produkcyjnym!źródło
Z mojego doświadczenia najgorsze, co wyszło z trybu Release, to niejasne „błędy wydania”. Ponieważ IL (język pośredni) jest zoptymalizowany w trybie Release, istnieje możliwość wystąpienia błędów, które nie pojawiłyby się w trybie Debugowania. Istnieją inne pytania SO dotyczące tego problemu: Typowe przyczyny błędów w wersji nie występują w trybie debugowania
Zdarzyło mi się to raz lub dwa razy, gdy prosta aplikacja konsolowa działałaby doskonale w trybie debugowania, ale przy tych samych danych wejściowych błąd występowałby w trybie zwolnienia. Te błędy są BARDZO trudne do debugowania (z definicji tryb Release, jak na ironię).
źródło
Powiedziałbym, że 1) w dużej mierze zależy od twojego wdrożenia. Zwykle różnica nie jest tak duża. Wykonałem wiele pomiarów i często nie widziałem różnicy. Jeśli używasz niezarządzanego kodu, wielu ogromnych tablic i tym podobnych, różnica w wydajności jest nieco większa, ale nie inny świat (jak w C ++). 2) Zwykle w kodzie wydania wyświetlanych jest mniej błędów (wyższa tolerancja), dlatego przełącznik powinien działać poprawnie.
źródło
źródło