Udostępnij za pośrednictwem


Pisanie aplikacji zarządzanych High-Performance: primer

 

Gregor Noriskin
Zespół ds. wydajności środowiska Microsoft CLR

Czerwiec 2003 r.

Dotyczy:
   Microsoft® .NET Framework

Krótki opis: Dowiedz się więcej o środowisku uruchomieniowym języka wspólnego .NET Framework z perspektywy wydajności. Dowiedz się, jak zidentyfikować najlepsze rozwiązania dotyczące wydajności kodu zarządzanego i jak mierzyć wydajność aplikacji zarządzanej. (19 stron drukowanych)

Pobierz profilera CLR. (330 KB)

Zawartość

Żonglowanie jako metafora tworzenia oprogramowania
Środowisko uruchomieniowe języka wspólnego platformy .NET
Zarządzane dane i moduł odśmiecjący pamięci
Profile alokacji
Interfejs API profilowania i profiler CLR
Hostowanie kontrolera domeny serwera
Finalizacji
Wzorzec usuwania
Uwaga dotycząca słabych odwołań
Zarządzany kod i interfejs JIT środowiska CLR
Typy wartości
Obsługa wyjątków
Wątkowanie i synchronizacja
Odbicie
Opóźnione powiązanie
Zabezpieczenia
Wywołanie międzyoperacowe modelu COM i platformy
Liczniki wydajności
Inne narzędzia
Podsumowanie
Zasoby

Żonglowanie jako metafora tworzenia oprogramowania

Żonglowanie to świetna metafora opisująca proces tworzenia oprogramowania. Żonglowanie zwykle wymaga co najmniej trzech elementów, choć nie ma górnego limitu liczby elementów, które można spróbować żonglować. Kiedy zaczynasz uczyć się, jak żonglować, znajdziesz, że watch każdej piłki indywidualnie, jak złapać i rzucić je. W miarę postępu zaczynasz skupiać się na przepływie piłek, w przeciwieństwie do każdej indywidualnej piłki. Kiedy opanowałeś żonglowanie, możesz po raz kolejny skupić się na jednej piłce, równoważąc piłkę na nosie, kontynuując żonglowanie innych. Wiesz intuicyjnie, gdzie kulki będą i można umieścić rękę w odpowiednim miejscu, aby złapać i rzucić je. Więc jak to jest tworzenie oprogramowania?

Różne role w procesie tworzenia oprogramowania żongluje różnymi "trójcami"; Menedżerowie projektów i programów żongluj funkcje, zasoby i czas oraz deweloperzy oprogramowania żongluj poprawność, wydajność i zabezpieczenia. Zawsze można spróbować żonglować więcej przedmiotów, ale jak każdy student żonglowania może potwierdzić, dodając jedną piłkę sprawia, że wykładniczo trudniejsze, aby utrzymać piłki w powietrzu. Technicznie, jeśli żonglujesz mniej niż trzy piłki, nie żonglujesz w ogóle. Jeśli jako deweloper oprogramowania nie rozważasz poprawności, wydajności i zabezpieczeń pisanego kodu, przypadek może się okazać, że nie wykonujesz pracy. Kiedy początkowo zaczniesz rozważać poprawność, wydajność i zabezpieczenia, musisz skupić się na jednym aspekcie naraz. Gdy stają się częścią codziennej praktyki, okaże się, że nie musisz skupić się na konkretnym aspektie, będą one po prostu częścią sposobu pracy. Po opanowaniu ich będziesz w stanie intuicyjnie dokonać kompromisów i odpowiednio skupić swoje wysiłki. A podobnie jak w przypadku żonglowania, praktyka jest kluczem.

Pisanie kodu o wysokiej wydajności ma własną trójcę; Ustawianie celów, pomiarów i zrozumienia platformy docelowej. Jeśli nie wiesz, jak szybko musi być twój kod, jak będziesz wiedzieć, kiedy skończysz? Jeśli nie mierzysz i profilujesz kodu, jak będziesz wiedzieć, kiedy zostały spełnione twoje cele, czy też dlaczego nie spełniasz celów? Jeśli nie rozumiesz docelowej platformy, w jaki sposób będziesz wiedzieć, co należy zoptymalizować w przypadku, gdy nie spełniasz Twoich celów. Te zasady mają zastosowanie do opracowywania kodu o wysokiej wydajności w ogóle, niezależnie od platformy docelowej. Żaden artykuł na temat pisania kodu o wysokiej wydajności nie zostanie ukończony bez wzmianki o tej trójcy. Chociaż wszystkie trzy są równie znaczące, ten artykuł koncentruje się na dwóch ostatnich aspektach, ponieważ mają zastosowanie do pisania aplikacji o wysokiej wydajności, które dotyczą .NET Framework firmy Microsoft®.

Podstawowe zasady pisania kodu o wysokiej wydajności na dowolnej platformie to:

  1. Ustawianie celów wydajności
  2. Miara, miara, a następnie mierzenie nieco więcej
  3. Informacje o platformach sprzętowych i programowych przeznaczonych dla aplikacji

Środowisko uruchomieniowe języka wspólnego platformy .NET

Podstawowym elementem .NET Framework jest środowisko uruchomieniowe języka wspólnego (CLR). ClR udostępnia wszystkie usługi środowiska uruchomieniowego dla kodu; Kompilacja just in time, zarządzanie pamięcią, zabezpieczenia i wiele innych usług. ClR został zaprojektowany tak, aby był wysokowydajny. Powiedział, że istnieją sposoby, aby skorzystać z tej wydajności i sposobów, które można utrudnić.

Celem tego artykułu jest przedstawienie przeglądu środowiska uruchomieniowego języka wspólnego z perspektywy wydajności, zidentyfikowanie najlepszych rozwiązań dotyczących wydajności kodu zarządzanego oraz pokazanie, jak można mierzyć wydajność aplikacji zarządzanej. Ten artykuł nie jest wyczerpującą dyskusją na temat cech wydajności .NET Framework. Na potrzeby tego artykułu zdefiniuję wydajność, aby uwzględnić przepływność, skalowalność, czas uruchamiania i użycie pamięci.

Zarządzane dane i moduł odśmiecjący pamięci

Jedną z głównych obaw deweloperów związanych z używaniem kodu zarządzanego w aplikacjach o krytycznym znaczeniu dla wydajności jest koszt zarządzania pamięcią środowiska CLR, który jest wykonywany przez moduł odśmiecenia pamięci (GC). Koszt zarządzania pamięcią jest funkcją kosztu alokacji pamięci skojarzonej z wystąpieniem typu, kosztem zarządzania tą pamięcią w okresie istnienia wystąpienia oraz kosztem zwolnienia tej pamięci, gdy nie jest już potrzebny.

Alokacja zarządzana jest zwykle bardzo tania; w większości przypadków trwa mniej czasu niż C/C++ malloc lub new. Wynika to z faktu, że CLR nie musi skanować bezpłatnej listy, aby znaleźć następny ciągły blok pamięci wystarczająco duży, aby pomieścić nowy obiekt; utrzymuje wskaźnik do następnej wolnej pozycji w pamięci. Można myśleć o zarządzanych alokacjach sterty jako "stos jak". Alokacja może spowodować, że kolekcja musi zwolnić pamięć, aby przydzielić nowy obiekt, w którym przypadku alokacja jest droższa niż lub mallocnew. Obiekty przypięte mogą również mieć wpływ na koszt alokacji. Przypięte obiekty są obiektami, które zostały poinstruowane, aby nie były przenoszone podczas kolekcji, zazwyczaj dlatego, że adres obiektu został przekazany do natywnego interfejsu API.

W przeciwieństwie do elementu malloc lub newistnieje koszt związany z zarządzaniem pamięcią w okresie istnienia obiektu. ClR GC jest generowania, co oznacza, że cały stert nie zawsze jest zbierany. Jednak GC nadal musi wiedzieć, czy jakiekolwiek obiekty aktywne w pozostałej części obiektów głównych stert w części stert, która jest zbierana. Pamięć zawierająca obiekty przechowujące odwołania do obiektów w młodszych pokoleniach jest kosztowna do zarządzania w okresie istnienia obiektów.

GC to znak pokoleniowy i zamiatanie modułu odśmiecniania pamięci. Zarządzana sterta zawiera trzy generacje; Generacja 0 zawiera wszystkie nowe obiekty, generacja 1 zawiera nieco dłuższe obiekty, a generacja 2 zawiera obiekty długotrwałe. GC będzie zbierać najmniejszą część stert możliwe do zwolnienia wystarczającej ilości pamięci, aby aplikacja mogła kontynuować. Kolekcja generacji obejmuje kolekcję wszystkich młodszych pokoleń, w tym przypadku kolekcja generacji 1 również zbiera generację 0. Generacja 0 jest dynamicznie większa zgodnie z rozmiarem pamięci podręcznej procesora i szybkością alokacji aplikacji, a zwykle zajmuje mniej niż 10 milisekund do zebrania. Generacja 1 jest dynamiczna w zależności od szybkości alokacji aplikacji i zwykle trwa od 10 do 30 milisekund do zebrania. Rozmiar generacji 2 będzie zależeć od profilu alokacji aplikacji, ponieważ czas potrzebny na zebranie. Jest to te kolekcje generacji 2, które mają największy wpływ na koszt wydajności zarządzania pamięcią aplikacji.

WSKAZÓWKA GC jest dostrajanie samodzielne i dostosuje się zgodnie z wymaganiami dotyczącymi pamięci aplikacji. W większości przypadków programowe wywoływanie GC utrudni to dostrajanie. "Pomoc" GC przez wywołanie GC. Funkcja Collect prawdopodobnie nie poprawi wydajności aplikacji.

GC może przenieść obiekty na żywo podczas kolekcji. Jeśli te obiekty są duże, koszt relokacji jest wysoki, więc te obiekty są przydzielane w specjalnym obszarze sterta o nazwie Sterta dużego obiektu. Sterta dużych obiektów jest zbierana, ale nie jest kompaktowana, na przykład duże obiekty nie są przenosine. Duże obiekty to te obiekty, które są większe niż 80 kb. Należy pamiętać, że może to ulec zmianie w przyszłych wersjach środowiska CLR. Gdy należy zebrać stertę dużych obiektów, wymusza pełną kolekcję, a stertę dużych obiektów jest zbierana podczas kolekcji Gen 2. Alokacja i śmiertelność obiektów w stercie dużych obiektów może mieć znaczący wpływ na koszt wydajności zarządzania pamięcią aplikacji.

Profile alokacji

Ogólny profil alokacji aplikacji zarządzanej określa, jak ciężko moduł odśmiecenia pamięci musi działać w celu zarządzania pamięcią skojarzzoną z aplikacją. Im trudniej jest pracować z zarządzaniem pamięcią, tym większa liczba cykli procesora CPU zajmuje GC, a tym mniej czasu procesor cpu będzie spędzać na uruchomieniu kodu aplikacji. Profil alokacji jest funkcją liczby przydzielonych obiektów, rozmiaru tych obiektów i ich okresów istnienia. Najbardziej oczywistym sposobem złagodzenia ciśnienia GC jest po prostu przydzielenie mniejszej liczby obiektów. Aplikacje zaprojektowane pod kątem rozszerzalności, modułowości i ponownego użycia przy użyciu technik projektowania zorientowanych obiektów niemal zawsze spowodują zwiększenie liczby alokacji. Istnieje kara za abstrakcję i "elegancję".

Profil alokacji przyjaznej GC będzie miał niektóre obiekty przydzielone na początku aplikacji, a następnie przetrwać przez cały okres istnienia aplikacji, a następnie wszystkie inne obiekty są krótkotrwałe. Długotrwałe obiekty będą zawierać kilka odwołań do obiektów krótkotrwałych. Ponieważ profil alokacji odbiega od tego, GC będzie musiał pracować trudniej, aby zarządzać pamięcią aplikacji.

Profil alokacji GC-unfriendly będzie miał wiele obiektów ocalałych w generacji 2, a następnie umierających, lub będzie miał wiele krótkotrwałych obiektów przydzielanych w stercie dużego obiektu. Obiekty, które przetrwają wystarczająco długo, aby dostać się do generacji 2, a następnie umrzeć, są najdroższe do zarządzania. Jak wspomniano wcześniej, obiekty w starszych pokoleniach, które zawierają odwołania do obiektów w młodszych pokoleniach podczas GC, również zwiększają koszt kolekcji.

Typowy rzeczywisty profil alokacji będzie gdzieś między dwoma profilami alokacji wymienionymi powyżej. Ważną metryą profilu alokacji jest procent całkowitego czasu procesora CPU, który jest spędzany w GC. Tę liczbę można pobrać z pamięci środowiska CLR platformy .NET: % czasu w liczniku wydajności GC. Jeśli średnia wartość tego licznika jest wyższa niż 30%, prawdopodobnie należy rozważyć przyjrzenie się bliżej profilowi alokacji. Niekoniecznie oznacza to, że profil alokacji jest "zły"; istnieją pewne aplikacje intensywnie korzystające z pamięci, w których ten poziom GC jest niezbędny i odpowiedni. Ten licznik powinien być pierwszą rzeczą, na którą patrzysz, jeśli wystąpią problemy z wydajnością; powinien natychmiast pokazać, czy twój profil alokacji jest częścią problemu.

WSKAZÓWKA Jeśli pamięć środowiska .NET CLR: % czasu w liczniku wydajności GC wskazuje, że aplikacja wydaje średnio ponad 30% czasu w GC, należy przyjrzeć się bliżej profilowi alokacji.

WSKAZÓWKA Aplikacja przyjazna GC będzie miała znacznie więcej kolekcji generacji 0 niż kolekcja 2. generacji. Ten współczynnik można ustanowić, porównując pamięć ŚRODOWISKA CLR NET: # Gen 0 Kolekcje i Pamięć NET CLR: # Gen 2 Kolekcje liczniki wydajności.

Interfejs API profilowania i profiler CLR

ClR zawiera zaawansowany interfejs API profilowania, który umożliwia innym firmom pisanie niestandardowych profilatorów dla aplikacji zarządzanych. ClR Profiler to nieobsługiwane narzędzie do profilowania alokacji, napisane przez zespół ds. produktu CLR, które korzysta z tego interfejsu API profilowania. Aplikacja CLR Profiler umożliwia deweloperom wyświetlanie profilu alokacji zarządzanych aplikacji.

Rysunek 1 Okno główne profilera CLR

Profiler CLR zawiera wiele bardzo przydatnych widoków profilu alokacji, w tym histogram przydzielonych typów, alokacji i wywołań grafów, wiersz czasu przedstawiający kontrolery domeny różnych pokoleń oraz wynikowy stan sterty zarządzanej po tych kolekcjach oraz drzewo wywołań przedstawiające alokacje poszczególnych metod i obciążenia zestawu.

Rysunek 2. Wykres alokacji profilera CLR

WSKAZÓWKA Aby uzyskać szczegółowe informacje na temat korzystania z profilera CLR, zobacz plik readme, który znajduje się w pliku zip.

Należy pamiętać, że profiler CLR ma wysokie obciążenie i znacząco zmienia charakterystykę wydajności aplikacji. Pojawiające się błędy stresu prawdopodobnie znikną po uruchomieniu aplikacji przy użyciu profilera CLR.

Hostowanie kontrolera domeny serwera

Dwa różne moduły odśmieceń pamięci są dostępne dla środowiska CLR: stacja robocza GC i GC serwera. Konsola i Windows Forms aplikacje hostuje GC stacji roboczej, a ASP.NET hostuje GC serwera. Serwer GC jest zoptymalizowany pod kątem przepływności i skalowalności wielu procesorów. Serwer GC wstrzymuje wszystkie wątki uruchomione w kodzie zarządzanym przez cały czas trwania kolekcji, w tym zarówno fazy Mark, jak i Sweep, a GC odbywa się równolegle we wszystkich procesorach dostępnych dla procesu w dedykowanych wątkach o wysokim priorytycie procesora CPU-affinitized. Jeśli wątki uruchamiają kod natywny podczas GC, te wątki są wstrzymane tylko wtedy, gdy wywołanie natywne zwraca. Jeśli tworzysz aplikację serwera, która będzie uruchamiana na maszynach wieloprocesorowych, zdecydowanie zaleca się użycie kontrolera GC serwera. Jeśli aplikacja nie jest hostowana przez ASP.NET, musisz napisać aplikację natywną, która jawnie hostuje clR.

WSKAZÓWKA Jeśli tworzysz skalowalne aplikacje serwera, hostuje GC serwera. Zobacz Implementowanie niestandardowego hosta środowiska uruchomieniowego języka wspólnego dla aplikacji zarządzanej.

Stacja robocza GC jest zoptymalizowana pod kątem małych opóźnień, co jest zwykle wymagane dla aplikacji klienckich. Nie chce zauważalnego wstrzymania w aplikacji klienckiej podczas GC, ponieważ zazwyczaj wydajność klienta nie jest mierzona przez nieprzetworzonej przepływności, ale raczej przez postrzeganą wydajność. Stacja robocza GC wykonuje współbieżną GC, co oznacza, że wykonuje fazę oznaczania, gdy kod zarządzany jest nadal uruchomiony. GC wstrzyma tylko wątki z uruchomionym kodem zarządzanym, gdy musi wykonać fazę zamiatania. W usłudze GC stacji roboczej GC odbywa się tylko w jednym wątku i dlatego tylko w jednym procesorze.

Finalizacji

ClR zapewnia mechanizm, w którym czyszczenie jest wykonywane automatycznie przed zwolnieniem pamięci skojarzonej z wystąpieniem typu. Ten mechanizm jest nazywany finalizacją. Zazwyczaj finalizacja jest używana do wydawania zasobów natywnych, w tym przypadku Połączenia z bazą danych lub dojścia systemu operacyjnego, które są używane przez obiekt.

Finalizacja jest kosztowną funkcją i zwiększa presję wywieraną na GC. GC śledzi obiekty, które wymagają finalizacji w kolejce finalizowalnej. Jeśli podczas kolekcji GC znajdzie obiekt, który nie jest już żywy, ale wymaga sfinalizowania, wpis tego obiektu w kolejce finalizowalnej zostanie przeniesiony do kolejki FReachable. Finalizacja odbywa się w osobnym wątku nazywanym wątkiem finalizatora. Ponieważ cały stan obiektu może być wymagany podczas wykonywania finalizatora, obiekt i wszystkie obiekty, które wskazuje, są promowane do następnej generacji. Pamięć skojarzona z obiektem lub grafem obiektów jest zwalniana tylko podczas następującego GC.

Zasoby, które należy zwolnić, powinny być opakowane w możliwie najmniejszy obiekt Finalizable; na przykład jeśli klasa wymaga odwołań zarówno do zarządzanych, jak i niezarządzanych zasobów, należy owinąć niezarządzane zasoby w nowej klasie Finalizable i utworzyć jej składową klasy. Klasa nadrzędna nie powinna być finalizowalna. Oznacza to, że zostanie podwyższona tylko klasa zawierająca niezarządzane zasoby (przy założeniu, że nie przechowujesz odwołania do klasy nadrzędnej w klasie zawierającej zasoby niezarządzane). Inną rzeczą, o której należy pamiętać, jest to, że istnieje tylko jeden wątek finalizacji. Jeśli finalizator powoduje zablokowanie tego wątku, kolejne finalizatory nie zostaną wywołane, zasoby nie zostaną zwolnione, a aplikacja będzie wyciekać.

WSKAZÓWKA Finalizatory powinny być jak najprostsze i nigdy nie powinny blokować.

WSKAZÓWKA Umożliwia sfinalizowanie tylko klasy otoki wokół niezarządzanych obiektów, które wymagają czyszczenia.

Finalizacja może być uważana za alternatywę dla zliczania odwołań. Obiekt, który implementuje zliczanie odwołań, śledzi liczbę innych obiektów odwołujących się do niego (co może prowadzić do niektórych bardzo znanych problemów), dzięki czemu może zwolnić swoje zasoby, gdy jego liczba odwołań wynosi zero. ClR nie implementuje zliczania odwołań, dlatego musi zapewnić mechanizm automatycznego wydawania zasobów, gdy nie są przechowywane żadne odwołania do obiektu. Finalizacja jest tym mechanizmem. Finalizacja jest zwykle wymagana tylko w przypadku, gdy okres istnienia obiektu, który wymaga czyszczenia, nie jest jawnie znany.

Wzorzec usuwania

W przypadku, gdy okres istnienia obiektu jest jawnie znany, niezarządzane zasoby skojarzone z obiektem powinny być chętnie zwalniane. Jest to nazywane "dysponowaniem" obiektu. Wzorzec usuwania jest implementowany za pośrednictwem interfejsu IDisposable (choć zaimplementowanie go samodzielnie byłoby proste). Jeśli chcesz udostępnić chętną finalizację dla klasy, na przykład udostępnić wystąpienia klasy do dyspozycji, musisz mieć obiekt implementować interfejs IDisposable i zapewnić implementację metody Dispose . W metodzie Dispose wywołasz ten sam kod oczyszczania, który znajduje się w finalizatorze i poinformujesz GC, że nie musi już sfinalizować obiektu przez wywołanie GC. SuppressFinalization , metoda. Dobrym rozwiązaniem jest, aby zarówno metoda Dispose , jak i finalizer wywoływać wspólną funkcję finalizacji, aby zachować tylko jedną wersję kodu czyszczenia. Ponadto jeśli semantyka obiektu jest taka, że metoda Close będzie bardziej logiczna niż metoda Dispose , należy również zaimplementować metodę Close ; w takim przypadku połączenie bazy danych lub gniazdo są logicznie "zamknięte". Funkcja Close może po prostu wywołać metodę Dispose .

Zawsze dobrym rozwiązaniem jest zapewnienie metody Dispose dla klas z finalizatorem; nikt nie może być pewien, jak ta klasa będzie używana, na przykład, czy jego okres istnienia będzie jawnie znany, czy nie. Jeśli klasa, której używasz, implementuje wzorzec Dispose i jawnie wiesz, kiedy skończysz z obiektem, na pewno wywołaj metodę Dispose.

WSKAZÓWKA Podaj metodę Dispose dla wszystkich klas, które można sfinalizować.

WSKAZÓWKA Pomiń finalizację w metodzie Dispose .

WSKAZÓWKA Wywołaj typową funkcję oczyszczania.

WSKAZÓWKA Jeśli obiekt, którego używasz, implementuje funkcję IDisposable i wiesz, że obiekt nie jest już potrzebny, wywołaj metodę Dispose.

Język C# zapewnia bardzo wygodny sposób automatycznego usuwania obiektów. Słowo using kluczowe umożliwia zidentyfikowanie bloku kodu, po którym funkcja Dispose będzie wywoływana na wielu obiektach jednorazowych.

Język C#używa słowa kluczowego

using(DisposableType T)
{
   //Do some work with T
}
//T.Dispose() is called automatically

Uwaga dotycząca słabych odwołań

Każde odwołanie do obiektu znajdującego się na stosie, w rejestrze, w innym obiekcie lub w jednym z pozostałych elementów głównych GC zachowa obiekt żywy podczas GC. Jest to zazwyczaj bardzo dobra rzecz, biorąc pod uwagę, że zwykle oznacza to, że aplikacja nie jest wykonywana z tym obiektem. Istnieją jednak przypadki, w których chcesz mieć odwołanie do obiektu, ale nie chcesz wpływać na jego okres istnienia. W takich przypadkach CLR udostępnia mechanizm o nazwie Słabe odwołania, aby to zrobić. Każde silne odwołanie — na przykład odwołanie, które odwołuje obiekt — może zostać przekształcone w słabe odwołanie. Przykładem użycia słabych odwołań jest utworzenie zewnętrznego obiektu kursora, który może przechodzić przez strukturę danych, ale nie powinien mieć wpływu na okres istnienia obiektu. Innym przykładem jest utworzenie pamięci podręcznej, która jest opróżniona, gdy występuje ciśnienie pamięci; na przykład w przypadku wystąpienia GC.

Tworzenie słabego odwołania w języku C#

MyRefType mrt = new MyRefType();
//...

//Create weak reference
WeakReference wr = new WeakReference(mrt); 
mrt = null; //object is no longer rooted
//...

//Has object been collected?
if(wr.IsAlive)
{
   //Get a strong reference to the object
   mrt = wr.Target;
   //object is rooted and can be used again
}
else
{
   //recreate the object
   mrt = new MyRefType();
}

Zarządzany kod i interfejs JIT środowiska CLR

Zarządzane zestawy, które są jednostką dystrybucji kodu zarządzanego, zawierają język niezależny od procesora o nazwie Microsoft Intermediate Language (MSIL lub IL). ClR Just-In-Time (JIT) kompiluje IL do zoptymalizowanych natywnych instrukcji X86. JIT jest optymalizatorem kompilatora, ale ponieważ kompilacja odbywa się w czasie wykonywania, a tylko po raz pierwszy wywoływana jest metoda, liczba optymalizacji, które należy zrównoważyć względem czasu potrzebnego do wykonania kompilacji. Zazwyczaj nie jest to krytyczne dla aplikacji serwerowych, ponieważ czas uruchamiania i czas odpowiedzi nie jest zazwyczaj problemem, ale ma kluczowe znaczenie dla aplikacji klienckich. Należy pamiętać, że czas uruchamiania można poprawić, wykonując kompilację w czasie instalacji przy użyciu NGEN.exe.

Wiele optymalizacji wykonanych przez JIT nie ma skojarzonych z nimi wzorców programowych, na przykład nie można jawnie kodowania dla nich, ale istnieje liczba, które to robią. W następnej sekcji omówiono niektóre z tych optymalizacji.

WSKAZÓWKA Zwiększ czas uruchamiania aplikacji klienckich, kompilując aplikację w czasie instalacji przy użyciu narzędzia NGEN.exe.

Inlining metody

Istnieje koszt związany z wywołaniami metod; Argumenty muszą być wypychane na stos lub przechowywane w rejestrach, należy wykonać metodę prolog i epilog itd. Koszt tych wywołań można uniknąć dla niektórych metod, po prostu przenosząc treść metody metody wywoływanej do treści obiektu wywołującego. Jest to nazywane podszewką metody. Funkcja JIT używa wielu heurystyki, aby zdecydować, czy metoda powinna być w kolejce. Poniżej znajduje się lista bardziej znaczących elementów (należy pamiętać, że nie jest to wyczerpujące):

  • Metody, które są większe niż 32 bajty IL, nie zostaną podkreślone.
  • Funkcje wirtualne nie są podkreślone.
  • Metody, które mają złożoną kontrolę przepływu, nie będą w kolejce. Złożona kontrola przepływu to dowolna kontrolka przepływu inna niż if/then/else; w tym przypadku switch lub while.
  • Metody, które zawierają bloki obsługi wyjątków, nie są podkreślone, chociaż metody, które zgłaszają wyjątki, nadal są kandydatami do podkreślenia.
  • Jeśli którykolwiek z argumentów formalnych metody jest strukturami, metoda nie zostanie podkreślona.

Starannie rozważyłbym jawne kodowanie tych heurystyki, ponieważ mogą one ulec zmianie w przyszłych wersjach JIT. Nie naruszaj poprawności metody, aby spróbować zagwarantować, że zostanie ona podkreślona. Warto zauważyć, że inline słowa kluczowe i __inline w języku C++ nie gwarantują, że kompilator będzie w tekście metody (choć __forceinline nie).

Metody pobierania i ustawiania właściwości są zazwyczaj dobrymi kandydatami do tworzenia inliningu, ponieważ wszystko, co robią, zwykle inicjuje prywatne elementy członkowskie danych.

**WSKAZÓWKA **Nie naruszaj poprawności metody w celu zagwarantowania podkreślenia.

Eliminacja sprawdzania zakresu

Jedną z wielu zalet kodu zarządzanego jest automatyczne sprawdzanie zakresu; za każdym razem, gdy uzyskujesz dostęp do tablicy przy użyciu semantyki array[index], JIT emituje sprawdzanie, aby upewnić się, że indeks znajduje się w granicach tablicy. W kontekście pętli z dużą liczbą iteracji i niewielką liczbą instrukcji wykonanych na iterację te kontrole zakresu mogą być kosztowne. Istnieją przypadki, gdy funkcja JIT wykryje, że te kontrole zakresu są niepotrzebne i wyeliminowają kontrolę z treści pętli, sprawdzając ją tylko raz przed rozpoczęciem wykonywania pętli. W języku C# istnieje wzorzec programowy, aby upewnić się, że te testy zakresu zostaną wyeliminowane: jawnie przetestuj długość tablicy w instrukcji "for". Należy pamiętać, że subtelne odchylenia od tego wzorca spowodują, że sprawdzanie nie zostanie wyeliminowane, a w tym przypadku dodanie wartości do indeksu.

Eliminacja sprawdzania zakresu w języku C#

//Range check will be eliminated
for(int i = 0; i < myArray.Length; i++) 
{
   Console.WriteLine(myArray[i].ToString());
}

//Range check will NOT be eliminated
for(int i = 0; i < myArray.Length + y; i++) 
{ 
   Console.WriteLine(myArray[i+x].ToString());
}

Optymalizacja jest szczególnie zauważalna podczas wyszukiwania dużych poszarpanych tablic, na przykład, ponieważ zarówno kontrola zakresu wewnętrznej, jak i zewnętrznej pętli są wyeliminowane.

Optymalizacje wymagające śledzenia użycia zmiennych

Liczba optymalizacji kompilatora JIT wymaga, aby JIT śledzić użycie argumentów formalnych i zmiennych lokalnych; na przykład kiedy są one używane po raz pierwszy i po raz ostatni są używane w treści metody. W wersji 1.0 i 1.1 środowiska CLR istnieje ograniczenie 64 dla całkowitej liczby zmiennych, dla których JIT będzie śledzić użycie. Przykładem optymalizacji, która wymaga śledzenia użycia, jest wyrejestrowanie. Wyrejestrowanie polega na tym, że zmienne są przechowywane w rejestrach procesora, a nie w ramce stosu, na przykład w pamięci RAM. Dostęp do zmiennych wyrejestrowanych jest znacznie szybszy niż w przypadku, gdy znajdują się one w ramce stosu, nawet jeśli zmienna na ramce będzie znajdować się w pamięci podręcznej procesora. Tylko 64 zmienne będą brane pod uwagę w przypadku wyrejestrowania; wszystkie inne zmienne zostaną wypchnięte na stos. Istnieją inne optymalizacje niż wyrejestrowanie, które zależą od śledzenia użycia. Liczba formalnych argumentów i ustawień lokalnych metody powinna być przechowywana poniżej 64, aby zapewnić maksymalną liczbę optymalizacji JIT. Należy pamiętać, że ta liczba może ulec zmianie w przyszłych wersjach środowiska CLR.

WSKAZÓWKA Zachowaj krótkie metody. Istnieje wiele powodów, dla których jest to m.in. tworzenie, rejestrowanie i czas trwania JIT.

Inne optymalizacje JIT

Kompilator JIT wykonuje szereg innych optymalizacji: stałej i propagacji kopiowania, niezmiennego podnoszenia pętli i kilku innych. Nie ma jawnych wzorców programowania, których należy użyć do uzyskania tych optymalizacji; są bezpłatne.

Dlaczego te optymalizacje nie są widoczne w programie Visual Studio?

Jeśli używasz opcji Start z menu Debugowanie lub naciśnij klawisz F5, aby uruchomić aplikację w programie Visual Studio, niezależnie od tego, czy utworzono wersję wydania, czy debugowania, wszystkie optymalizacje JIT zostaną wyłączone. Po uruchomieniu aplikacji zarządzanej przez debuger, nawet jeśli nie jest to kompilacja debugowania aplikacji, JIT emituje nieoptymalizowane instrukcje x86. Jeśli chcesz, aby JIT emitował zoptymalizowany kod, uruchom aplikację z Eksploratora Windows lub użyj klawiszy CTRL+F5 z poziomu programu Visual Studio. Jeśli chcesz wyświetlić zoptymalizowany dezasemblacji i porównać go z nieoptymalizowanym kodem, możesz użyć cordbg.exe.

WSKAZÓWKA Użyj cordbg.exe, aby zobaczyć dezasemblacji zarówno zoptymalizowanego, jak i nieoptymalizowanego kodu emitowanego przez JIT. Po uruchomieniu aplikacji za pomocą cordbg.exe możesz ustawić tryb JIT, wpisując następujące polecenie:

(cordbg) mode JitOptimizations 1
JIT's will produce optimized code

(cordbg) mode JitOptimizations 0

JIT będzie generować kod debugowalny (nieoptymalny).

Typy wartości

ClR uwidacznia dwa różne zestawy typów, typów referencyjnych i typów wartości. Typy odwołań są zawsze przydzielane na zarządzanym stercie i są przekazywane przez odwołanie (jak wskazuje nazwa). Typy wartości są przydzielane na stosie lub śródliniowym w ramach obiektu na stercie i są domyślnie przekazywane przez wartość, choć można je również przekazać przy użyciu odwołania. Typy wartości są bardzo tanie do przydzielenia i przy założeniu, że są one przechowywane małe i proste, są tanie do przekazania jako argumenty. Dobrym przykładem odpowiedniego użycia typów wartości jest typ wartości punkt, który zawiera współrzędną x i y .

Typ wartości punktu

struct Point
{
   public int x;
   public int y;
   
   //
}

Typy wartości mogą być również traktowane jako obiekty; na przykład metody obiektów mogą być wywoływane na nich, mogą być rzutowane do obiektu lub przekazywane tam, gdzie obiekt jest oczekiwany. W takim przypadku typ wartości jest konwertowany na typ odwołania za pośrednictwem procesu o nazwie Boxing. Gdy typ wartości jest boxed, nowy obiekt jest przydzielany na zarządzanym stercie, a wartość jest kopiowana do nowego obiektu. Jest to kosztowna operacja i może zmniejszyć lub całkowicie negować wydajność uzyskaną przy użyciu typów wartości. Gdy typ Boxed jest niejawnie lub jawnie rzutowany z powrotem do typu wartości, jest on rozpiętywany.

Typ wartości pola/skrzynki odbiorczej

C#:

int BoxUnboxValueType()
{
   int i = 10;
   object o = (object)i; //i is Boxed
   return (int)o + 3; //i is Unboxed
}

MSIL:

.method private hidebysig instance int32
        BoxUnboxValueType() cil managed
{
  // Code size       20 (0x14)
  .maxstack  2
  .locals init (int32 V_0,
           object V_1)
  IL_0000:  ldc.i4.s   10
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  box        [mscorlib]System.Int32
  IL_0009:  stloc.1
  IL_000a:  ldloc.1
  IL_000b:  unbox      [mscorlib]System.Int32
  IL_0010:  ldind.i4
  IL_0011:  ldc.i4.3
  IL_0012:  add
  IL_0013:  ret
} // end of method Class1::BoxUnboxValueType

W przypadku implementowania niestandardowych typów wartości (struktura w języku C#) należy rozważyć zastąpienie metody ToString . Jeśli ta metoda nie zostanie zastąpiona, wywołanie metody ToString w typie wartości spowoduje, że typ ma być boxed. Dotyczy to również innych metod, które są dziedziczone z obiektu System.Object, w tym przypadku Equals, choć ToString jest prawdopodobnie najczęściej nazywaną metodą. Jeśli chcesz wiedzieć, czy typ wartości jest boxed, możesz wyszukać box instrukcję w MSIL przy użyciu narzędzia ildasm.exe (jak w powyższym fragmencie kodu).

Zastępowanie metody ToString() w języku C#w celu zapobiegania boxingowi

struct Point
{
   public int x;
   public int y;

   //This will prevent type being boxed when ToString is called
   public override string ToString()
   {
      return x.ToString() + "," + y.ToString();
   }
}

Należy pamiętać, że podczas tworzenia kolekcji , na przykład tablicalista zmiennoprzecinkowa, każdy element będzie boxed po dodaniu do kolekcji. Należy rozważyć użycie tablicy lub utworzenie niestandardowej klasy kolekcji dla typu wartości.

Niejawne boxing podczas korzystania z klas kolekcji w języku C#

ArrayList al = new ArrayList();
al.Add(42.0F); //Implicitly Boxed becuase Add() takes object
float f = (float)al[0]; //Unboxed

Obsługa wyjątków

Typowym rozwiązaniem jest użycie warunków błędu jako normalnego sterowania przepływem. W takim przypadku podczas próby programowego dodania użytkownika do wystąpienia usługi Active Directory można po prostu spróbować dodać użytkownika, a jeśli zostanie zwrócona E_ADS_OBJECT_EXISTS HRESULT, wiesz, że już istnieją w katalogu. Alternatywnie możesz wyszukać katalog użytkownika, a następnie dodać go tylko wtedy, gdy wyszukiwanie zakończy się niepowodzeniem.

To użycie błędów dla normalnego sterowania przepływem jest antywzór wydajności w kontekście CLR. Obsługa błędów w clR jest wykonywana z obsługą wyjątków ustrukturyzowanych. Zarządzane wyjątki są bardzo tanie, dopóki ich nie zgłosisz. W środowisku CLR, gdy zgłaszany jest wyjątek, wymagany jest przewodnik stosu, aby znaleźć odpowiednią procedurę obsługi wyjątków dla zgłaszanego wyjątku. Chodzenie stosem jest kosztowną operacją. Wyjątki powinny być używane jako ich nazwa; w wyjątkowych lub nieoczekiwanych okolicznościach.

**WSKAZÓWKA **Rozważ zwrócenie wyliczonego wyniku dla oczekiwanych wyników, w przeciwieństwie do zgłaszania wyjątku dla metod krytycznych dla wydajności.

**WSKAZÓWKA **Istnieje wiele liczników wydajności wyjątków CLR platformy .NET, które informują o liczbie wyjątków zgłaszanych w aplikacji.

**WSKAZÓWKA **Jeśli używasz VB.NET używasz wyjątków, a nie On Error Goto; obiekt błędu jest niepotrzebnym kosztem.

Wątkowanie i synchronizacja

ClR uwidacznia zaawansowane funkcje wątków i synchronizacji, w tym możliwość tworzenia własnych wątków, puli wątków i różnych elementów pierwotnych synchronizacji. Przed skorzystaniem z obsługi wątków w clR należy dokładnie rozważyć użycie wątków. Należy pamiętać, że dodanie wątków może w rzeczywistości zmniejszyć przepływność, a nie zwiększyć jej, i można mieć pewność, że zwiększy wykorzystanie pamięci. W aplikacjach serwera, które będą uruchamiane na maszynach z wieloma procesorami, dodanie wątków może znacznie poprawić przepływność poprzez równoległe wykonywanie (choć zależy to od tego, ile rywalizacji o blokadę odbywa się, na przykład serializacji wykonywania) i w aplikacjach klienckich, dodanie wątku do pokazywania aktywności i/lub postępu może poprawić postrzeganą wydajność (przy niewielkim koszcie przepływności).

Jeśli wątki w aplikacji nie są wyspecjalizowane dla określonego zadania lub mają specjalny stan skojarzony z nimi, należy rozważyć użycie puli wątków. Jeśli w przeszłości użyto puli wątków Win32, pula wątków CLR będzie ci bardzo znana. Istnieje jedno wystąpienie puli wątków na proces zarządzany. Pula wątków jest inteligentna o liczbie tworzonych wątków i dostraja się zgodnie z obciążeniem na maszynie.

Nie można omawiać wątków bez omawiania synchronizacji; wszystkie zyski przepływności, które wielowątkowanie może dać aplikacji może być negowane przez źle napisaną logikę synchronizacji. Stopień szczegółowości blokad może znacząco wpłynąć na ogólną przepływność aplikacji, zarówno ze względu na koszt tworzenia blokady i zarządzania nią, jak i fakt, że blokady mogą potencjalnie serializować wykonywanie. Użyję przykładu próby dodania węzła do drzewa, aby zilustrować ten punkt. Jeśli drzewo będzie współdzieloną strukturą danych, na przykład wiele wątków musi mieć do niego dostęp podczas wykonywania aplikacji i konieczne będzie zsynchronizowanie dostępu do drzewa. Możesz zablokować całe drzewo podczas dodawania węzła, co oznacza, że koszty tworzenia pojedynczej blokady są naliczane tylko przez koszt, ale inne wątki próbujące uzyskać dostęp do drzewa prawdopodobnie zablokują. Jest to przykład grubej blokady. Alternatywnie można zablokować każdy węzeł podczas przechodzenia przez drzewo, co oznaczałoby naliczenie kosztów utworzenia blokady na węzeł, ale inne wątki nie będą blokowane, chyba że podjęto próbę uzyskania dostępu do określonego węzła, który został zablokowany. Jest to przykład precyzyjnej blokady. Prawdopodobnie bardziej odpowiedni stopień szczegółowości blokady byłoby zablokowanie tylko podzadrzewa, na którym działasz. Należy pamiętać, że w tym przykładzie prawdopodobnie będziesz używać udostępnionej blokady (RWLock), ponieważ wielu czytelników powinno mieć możliwość uzyskania dostępu w tym samym czasie.

Najprostszym i najwyższym sposobem wykonywania zsynchronizowanych operacji jest użycie klasy System.Threading.Interlocked. Klasa Interlocked uwidacznia wiele operacji niepodzielnych niskiego poziomu: przyrost, dekrementacja, Exchange i CompareExchange.

Używanie klasy System.Threading.Interlocked w języku C#

using System.Threading;
//...
public class MyClass
{
   void MyClass() //Constructor
   {
      //Increment a global instance counter atomically
      Interlocked.Increment(ref MyClassInstanceCounter);
   }

   ~MyClass() //Finalizer
   {
      //Decrement a global instance counter atomically
      Interlocked.Decrement(ref MyClassInstanceCounter);
      //... 
   }
   //...
}

Prawdopodobnie najczęściej używanym mechanizmem synchronizacji jest sekcja Monitorowanie lub krytyczne. Blokada monitora może być używana bezpośrednio lub za pomocą słowa kluczowego lock w języku C#. Słowo lock kluczowe synchronizuje dostęp dla danego obiektu do określonego bloku kodu. Blokada monitora, która jest dość lekko kwestionowana, jest stosunkowo tańsza z perspektywy wydajności, ale staje się droższa, jeśli jest wysoce kwestionowana.

Słowo kluczowe blokady języka C#

//Thread will attempt to obtain the lock
//and block until it does
lock(mySharedObject)
{
   //A thread will only be able to execute the code
   //within this block if it holds the lock
}//Thread releases the lock

RwLock zapewnia mechanizm blokady udostępnionej: na przykład "czytelnicy" mogą udostępniać blokadę innym "czytelnikom", ale "pisarz" nie może. W przypadkach, w których ma to zastosowanie, RWLock może spowodować lepszą przepływność niż użycie monitora, co pozwoliłoby tylko jednemu czytnikowi lub zapisowi na uzyskanie blokady naraz. Przestrzeń nazw System.Threading zawiera również klasę Mutex. Mutex to pierwotny element synchronizacji, który umożliwia synchronizację między procesami. Należy pamiętać, że jest to znacznie droższe niż sekcja krytyczna i należy jej używać tylko w przypadku, gdy wymagana jest synchronizacja między procesami.

Odbicie

Odbicie to mechanizm zapewniany przez clR, który umożliwia programowe pobieranie informacji o typie w czasie wykonywania. Odbicie zależy w dużym stopniu od metadanych, które są osadzone w zarządzanych zestawach. Wiele interfejsów API odbicia wymaga wyszukiwania i analizowania metadanych, które są kosztownymi operacjami.

Interfejsy API odbicia można pogrupować w trzy zasobniki wydajności; porównanie typów, wyliczenie składowe i wywołanie składowe. Każdy z tych zasobników staje się coraz droższy. Operacje porównania typów — w tym przypadku typeof w języku C#, GetType, is, IsInstanceOfType itd. — są najtańszymi interfejsami API odbicia, choć nie są one w żaden sposób tanie. Wyliczenia składowe umożliwiają programowe sprawdzanie metod, właściwości, pól, zdarzeń, konstruktorów itd. klasy. Przykładem sytuacji, w której mogą być używane, jest w scenariuszach w czasie projektowania, w tym przypadku wyliczanie właściwości kontrolek sieci Web Customs dla przeglądarki właściwości w programie Visual Studio. Najdroższe interfejsy API odbicia to te, które umożliwiają dynamiczne wywoływanie składowych klasy lub dynamiczne emitowanie, JIT i wykonywanie metody. Na pewno istnieją scenariusze związane z późnym opóźnieniem, w których wymagane jest dynamiczne ładowanie zestawów, wystąpienia typów i wywołań metod, ale to luźne sprzężenie wymaga wyraźnego kompromisu wydajności. Ogólnie rzecz biorąc, interfejsy API odbicia należy unikać w ścieżkach kodu uwzględniających wydajność. Należy pamiętać, że chociaż nie używasz bezpośrednio odbicia, używany interfejs API może go używać. Należy więc również pamiętać o przejściowym użyciu interfejsów API odbicia.

Opóźnione powiązanie

Późne wywołania to przykład funkcji, która używa odbicia pod okładkami. Basic.NET wizualne i JScript.NET mają obsługę połączeń związanych z późnym opóźnieniem. Na przykład nie trzeba deklarować zmiennej przed jego użyciem. Obiekty powiązane z opóźnieniem są w rzeczywistości obiektami typu, a odbicie służy do konwertowania obiektu na poprawny typ w czasie wykonywania. Późne wywołanie to kolejność wielkości wolniejsza niż wywołanie bezpośrednie. Jeśli nie potrzebujesz specjalnie ograniczonego zachowania, należy unikać jego używania w ścieżkach kodu o znaczeniu krytycznym dla wydajności.

WSKAZÓWKA Jeśli używasz VB.NET i nie potrzebujesz jawnie późnego powiązania, możesz poinformować kompilatora, aby nie zezwalał na to, dołączając element Option Explicit On i Option Strict On w górnej części plików źródłowych. Te opcje wymuszają deklarowanie i silnie wpisywanie zmiennych i wyłączanie niejawnego rzutowania.

Zabezpieczenia

Bezpieczeństwo jest niezbędną i integralną częścią środowiska CLR i wiąże się z nim koszt wydajności. W przypadku, gdy kod jest w pełni zaufany, a zasady zabezpieczeń są domyślne, zabezpieczenia powinny mieć niewielki wpływ na przepływność i czas uruchamiania aplikacji. Częściowo zaufany kod — na przykład kod z Internetu lub strefy intranetowej lub zawężanie zestawu grantów MyComputer zwiększy koszt wydajności zabezpieczeń.

Wywołanie międzyoperacowe modelu COM i platformy

Com Interop and Platform Invoke uwidacznia natywne interfejsy API do kodu zarządzanego w niemal przezroczysty sposób; wywoływanie większości natywnych interfejsów API zwykle nie wymaga specjalnego kodu, ale może wymagać kilku kliknięć myszą. Jak można się spodziewać, istnieje koszt związany z wywoływaniem kodu natywnego z kodu zarządzanego i odwrotnie. Istnieją dwa składniki tego kosztu: stały koszt związany z wykonywaniem przejścia między kodem natywnym i zarządzanym oraz kosztem zmiennym skojarzonym z dowolnymi argumentami i wartościami zwracanymi, które mogą być wymagane. Stały wkład w koszt zarówno dla międzyoperaktowych modelu COM, jak i wywołania P/Invoke jest niewielki: zazwyczaj mniej niż 50 instrukcji. Koszt marshallingu do i z typów zarządzanych będzie zależeć od tego, jak różne reprezentacje znajdują się po obu stronach granicy. Typy wymagające znacznej ilości przekształceń będą droższe. Na przykład wszystkie ciągi w clR to ciągi Unicode. Jeśli wywołujesz interfejs API Win32 za pośrednictwem metody P/Invoke, która oczekuje tablicy znaków ANSI, każdy znak w ciągu musi zostać zawęziony. Jeśli jednak zarządzana tablica całkowita jest przekazywana, gdzie oczekiwana jest macierz natywna liczba całkowita, nie jest wymagane żadne marshalling.

Ponieważ istnieje koszt wydajności związany z wywoływaniem kodu natywnego, upewnij się, że koszt jest uzasadniony. Jeśli zamierzasz nawiązać połączenie natywne, upewnij się, że praca wywołania natywnego uzasadnia koszt wydajności związany z wykonywaniem wywołania — zachowaj metody "fragmenty", a nie "czatty". Dobrym sposobem mierzenia kosztów wywołania natywnego jest mierzenie wydajności metody natywnej, która nie przyjmuje argumentów i nie ma wartości zwracanej, a następnie zmierz wydajność metody natywnej, którą chcesz wywołać. Różnica daje wskazanie kosztu marshallingu.

WSKAZÓWKA Wykonaj wywołania "Chunky" COM Interop i P/Invoke w przeciwieństwie do wywołań "Chatty" i upewnij się, że koszt wykonywania połączenia jest uzasadniony ilością pracy wykonywanej przez wywołanie.

Należy pamiętać, że nie ma modeli wątków skojarzonych z zarządzanymi wątkami. Gdy zamierzasz wykonać wywołanie międzyoperamentowe MODELU COM, musisz upewnić się, że wątek, na który ma zostać wykonane wywołanie, zostanie zainicjowany do poprawnego modelu wątkowania COM. Zazwyczaj odbywa się to przy użyciu atrybutów MTAThreadAttribute i STAThreadAttribute (choć można to również zrobić programowo).

Liczniki wydajności

Dla środowiska .NET CLR jest uwidocznionych wiele liczników wydajności systemu Windows. Te liczniki wydajności powinny być bronią dewelopera do wyboru podczas pierwszej diagnostyki problemu z wydajnością lub podczas próby zidentyfikowania cech wydajności aplikacji zarządzanej. Wspomniałem już o kilku licznikach, które odnoszą się do zarządzania pamięcią i wyjątków. Istnieją liczniki wydajności dla prawie każdego aspektu środowiska CLR i .NET Framework. Te liczniki wydajności są zawsze dostępne i nieinwazyjne; mają niskie nakłady pracy i nie zmieniają właściwości wydajności aplikacji.

Inne narzędzia

Oprócz liczników wydajności i profilera CLR, należy użyć konwencjonalnego profilera, aby ustalić, które metody w aplikacji zajmują najwięcej czasu i są nazywane najczęściej. Będą to najpierw metody zoptymalizowane. Dostępnych jest wiele profilów komercyjnych, które obsługują kod zarządzany, w tym DevPartner Studio Professional Edition 7.0 z oprogramowania Compuware i VTune™ Analizator wydajności 7.0 firmy Intel®. Compuware tworzy również bezpłatny profiler dla kodu zarządzanego o nazwie DevPartner Profiler Community Edition.

Podsumowanie

W tym artykule dopiero zaczyna się badanie środowiska CLR i .NET Framework z perspektywy wydajności. Istnieje wiele innych aspektów architektury środowiska CLR i .NET Framework, które będą wpływać na wydajność aplikacji. Najlepsze wskazówki, które mogę dać każdemu deweloperowi, to nie podejmowania żadnych założeń dotyczących wydajności platformy, dla której aplikacja jest przeznaczona, oraz używanych interfejsów API. Zmierz wszystko!

Szczęśliwa żonglowanie.

Zasoby