Omówienie profilowania
Profiler to narzędzie, które monitoruje wykonywanie innej aplikacji. Profiler środowiska uruchomieniowego języka wspólnego (CLR) to dynamiczna biblioteka linków (DLL), która składa się z funkcji odbierających komunikaty i wysyłania komunikatów do środowiska CLR przy użyciu interfejsu API profilowania. Biblioteka DLL profilera jest ładowana przez clR w czasie wykonywania.
Tradycyjne narzędzia profilowania koncentrują się na mierzeniu wykonywania aplikacji. Oznacza to, że mierzy czas spędzony w każdej funkcji lub użycie pamięci aplikacji w czasie. Interfejs API profilowania jest przeznaczony dla szerszej klasy narzędzi diagnostycznych, takich jak narzędzia pokrycia kodu, a nawet zaawansowane pomoce debugowania. Te zastosowania są ze względu na charakter diagnostyczny. Interfejs API profilowania nie tylko mierzy, ale także monitoruje wykonywanie aplikacji. Z tego powodu interfejs API profilowania nigdy nie powinien być używany przez samą aplikację, a wykonywanie aplikacji nie powinno zależeć od profilera (lub nie powinno to mieć na nie wpływu).
Profilowanie aplikacji CLR wymaga większej obsługi niż profilowanie konwencjonalnego skompilowanego kodu maszynowego. Dzieje się tak, ponieważ clR wprowadza takie pojęcia jak domeny aplikacji, odzyskiwanie pamięci, obsługa wyjątków zarządzanych, kompilacja kodu just in time (JIT) (konwertowanie wspólnego języka pośredniego lub CIL, kod na natywny kod maszyny) i podobne funkcje. Konwencjonalne mechanizmy profilowania nie mogą identyfikować ani dostarczać przydatnych informacji o tych funkcjach. Interfejs API profilowania zapewnia te brakujące informacje wydajnie, przy minimalnym wpływie na wydajność środowiska CLR i profilowanej aplikacji.
Kompilacja JIT w czasie wykonywania zapewnia dobre możliwości profilowania. Interfejs API profilowania umożliwia profilerowi zmianę strumienia kodu CIL w pamięci dla procedury przed skompilowaniem JIT. W ten sposób profiler może dynamicznie dodawać kod instrumentacji do określonych procedur wymagających dokładniejszego badania. Chociaż takie podejście jest możliwe w konwencjonalnych scenariuszach, znacznie łatwiej jest zaimplementować proces CLR przy użyciu interfejsu API profilowania.
Interfejs API profilowania
Zazwyczaj interfejs API profilowania służy do pisania profilera kodu, czyli programu monitorującego wykonywanie aplikacji zarządzanej.
Interfejs API profilowania jest używany przez bibliotekę DLL profilera, która jest ładowana do tego samego procesu, co profilowana aplikacja. Biblioteka DLL profilera implementuje interfejs wywołania zwrotnego (ICorProfilerCallback w programie .NET Framework w wersji 1.0 i 1.1, ICorProfilerCallback2 w wersji 2.0 lub nowszej). ClR wywołuje metody w tym interfejsie, aby powiadomić profilera o zdarzeniach w procesie profilowanym. Profiler może wywołać z powrotem do środowiska uruchomieniowego przy użyciu metod w interfejsach ICorProfilerInfo i ICorProfilerInfo2 w celu uzyskania informacji o stanie profilowanej aplikacji.
Uwaga
W tym samym procesie co profilowana aplikacja powinna działać tylko część zbierania danych rozwiązania profilera. Wszystkie interfejsy użytkownika i analiza danych powinny być wykonywane w osobnym procesie.
Na poniższej ilustracji przedstawiono sposób interakcji biblioteki DLL profilera z aplikacją, która jest profilowana i CLR.
Interfejsy powiadomień
Interfejsy powiadomień można traktować jako ICorProfilerCallback2 i ICorProfilerCallback2 . Te interfejsy składają się z metod, takich jak ClassLoadStarted, ClassLoadFinished i JITCompilationStarted. Za każdym razem, gdy CLR ładuje lub zwalnia klasę, kompiluje funkcję itd., wywołuje odpowiednią metodę w interfejsie lub ICorProfilerCallback2
profileraICorProfilerCallback
.
Na przykład profiler może mierzyć wydajność kodu za pomocą dwóch funkcji powiadomień: FunctionEnter2 i FunctionLeave2. Oznacza to tylko sygnatury czasowe każdego powiadomienia, gromadzi wyniki i generuje listę wskazującą, które funkcje zużywały najwięcej czasu procesora CPU lub zegara ściany podczas wykonywania aplikacji.
Interfejsy pobierania informacji
Inne główne interfejsy związane z profilowaniem to ICorProfilerInfo i ICorProfilerInfo2. Profiler wywołuje te interfejsy zgodnie z wymaganiami, aby uzyskać więcej informacji, aby pomóc w analizie. Na przykład za każdym razem, gdy CLR wywołuje funkcję FunctionEnter2 , dostarcza identyfikator funkcji. Profiler może uzyskać więcej informacji o tej funkcji, wywołując metodę ICorProfilerInfo2::GetFunctionInfo2 , aby odnaleźć klasę nadrzędną funkcji, jej nazwę itd.
Obsługiwane funkcje
Interfejs API profilowania zawiera informacje o różnych zdarzeniach i akcjach występujących w środowisku uruchomieniowym języka wspólnego. Te informacje umożliwiają monitorowanie wewnętrznych procesów i analizowanie wydajności aplikacji .NET Framework.
Interfejs API profilowania pobiera informacje o następujących akcjach i zdarzeniach występujących w środowisku CLR:
Zdarzenia uruchamiania i zamykania środowiska CLR.
Zdarzenia tworzenia i zamykania domeny aplikacji.
Zdarzenia ładowania i zwalniania zestawów.
Zdarzenia ładowania i zwalniania modułu.
Zdarzenia tworzenia i niszczenia tabel wirtualnych COM.
Kompilacja just in time (JIT) i zdarzenia pitching kodu.
Klasy ładujące i zwalniające zdarzenia.
Zdarzenia tworzenia i niszczenia wątków.
Zdarzenia wprowadzania i zamykania funkcji.
Wyjątki.
Przejścia między wykonywaniem kodu zarządzanego i niezarządzanych.
Przejścia między różnymi kontekstami środowiska uruchomieniowego.
Informacje o zawieszeniach środowiska uruchomieniowego.
Informacje o stosie pamięci środowiska uruchomieniowego i działaniu odzyskiwania pamięci.
Interfejs API profilowania może być wywoływany z dowolnego (niezarządzanego) języka zgodnego z protokołem COM.
Interfejs API jest wydajny w odniesieniu do użycia procesora CPU i pamięci. Profilowanie nie obejmuje zmian w profilowanej aplikacji, które są wystarczająco znaczące, aby spowodować wprowadzenie w błąd wyników.
Interfejs API profilowania jest przydatny zarówno w przypadku profilowania próbkowania, jak i profilowania bez próbkowania. Profiler próbkowania sprawdza profil przy regularnych znacznikach zegara, powiedzmy, na 5 milisekund od siebie. Profiler bez próbkowania jest informowany o zdarzeniu synchronicznie z wątkiem, który powoduje zdarzenie.
Nieobsługiwana funkcja
Interfejs API profilowania nie obsługuje następujących funkcji:
Niezarządzany kod, który musi być profilowany przy użyciu konwencjonalnych metod Win32. Jednak profiler CLR obejmuje zdarzenia przejścia w celu określenia granic między kodem zarządzanym i niezarządzanych.
Samodzielne modyfikowanie aplikacji, które modyfikują własny kod do celów, takich jak programowanie zorientowane na aspekty.
Sprawdzanie granic, ponieważ interfejs API profilowania nie udostępnia tych informacji. ClR zapewnia wewnętrzną obsługę sprawdzania granic całego kodu zarządzanego.
Profilowanie zdalne, które nie jest obsługiwane z następujących powodów:
Profilowanie zdalne wydłuża czas wykonywania. W przypadku korzystania z interfejsów profilowania należy zminimalizować czas wykonywania, aby nie wpływało to na wyniki profilowania. Jest to szczególnie istotne w przypadku monitorowania wydajności wykonywania. Jednak profilowanie zdalne nie jest ograniczeniem, gdy interfejsy profilowania są używane do monitorowania użycia pamięci lub uzyskiwania informacji w czasie wykonywania o ramkach stosu, obiektach itd.
Profiler kodu CLR musi zarejestrować co najmniej jeden interfejs wywołania zwrotnego ze środowiskiem uruchomieniowym na komputerze lokalnym, na którym jest uruchomiona profilowana aplikacja. Ogranicza to możliwość tworzenia zdalnego profilera kodu.
Wątki powiadomień
W większości przypadków wątek, który generuje zdarzenie, również wykonuje powiadomienia. Takie powiadomienia (na przykład FunctionEnter i FunctionLeave) nie muszą podawać jawnego ThreadID
elementu . Ponadto profiler może zdecydować się na użycie magazynu wątkowego lokalnego do przechowywania i aktualizowania bloków analizy zamiast indeksowania bloków analizy w magazynie globalnym na ThreadID
podstawie objętego wątku.
Należy pamiętać, że te wywołania zwrotne nie są serializowane. Użytkownicy muszą chronić swój kod przez utworzenie struktur danych bezpiecznych wątkowo i zablokowanie kodu profilera w razie potrzeby, aby zapobiec dostępowi równoległemu z wielu wątków. W związku z tym w niektórych przypadkach można otrzymać nietypową sekwencję wywołań zwrotnych. Załóżmy na przykład, że aplikacja zarządzana duplikuje dwa wątki, które wykonują identyczny kod. W takim przypadku istnieje możliwość odebrania zdarzenia ICorProfilerCallback::JITCompilationStarted dla jakiejś funkcji z jednego wątku i FunctionEnter
wywołania zwrotnego z innego wątku przed odebraniem wywołania zwrotnego ICorProfilerCallback::JITCompilationFinished wywołania zwrotnego. W takim przypadku użytkownik otrzyma FunctionEnter
wywołanie zwrotne dla funkcji, która mogła nie być jeszcze skompilowana w pełni just-in-time (JIT).
Zabezpieczenia
Biblioteka DLL profilera to niezarządzana biblioteka DLL, która jest uruchamiana jako część aparatu wykonywania środowiska uruchomieniowego języka wspólnego. W związku z tym kod w pliku DLL profilera nie podlega ograniczeniom zabezpieczeń dostępu do kodu zarządzanego. Jedynymi ograniczeniami biblioteki DLL profilera są te nałożone przez system operacyjny na użytkownika, który korzysta z profilowanej aplikacji.
Autorzy profilera powinni podjąć odpowiednie środki ostrożności, aby uniknąć problemów związanych z zabezpieczeniami. Na przykład podczas instalacji biblioteka DLL profilera powinna zostać dodana do listy kontroli dostępu (ACL), aby złośliwy użytkownik nie mógł go zmodyfikować.
Łączenie kodu zarządzanego i niezarządzanego w profilerze kodu
Niepoprawnie napisany profiler może powodować odwołania cykliczne do samego siebie, co powoduje nieprzewidywalne zachowanie.
Przegląd interfejsu API profilowania CLR może spowodować wrażenie, że można napisać profiler zawierający zarządzane i niezarządzane składniki wywołujące się za pośrednictwem międzyoperacjności modelu COM lub wywołań pośrednich.
Chociaż jest to możliwe z perspektywy projektu, interfejs API profilowania nie obsługuje składników zarządzanych. Profiler CLR musi być całkowicie niezarządzany. Próby połączenia zarządzanego i niezarządzanego kodu w profilerze CLR mogą spowodować naruszenia dostępu, niepowodzenie programu lub zakleszczenia. Zarządzane składniki profilera będą uruchamiać zdarzenia z powrotem do ich niezarządzanych składników, co spowoduje ponowne wywołanie zarządzanych składników, co spowoduje odwołania cykliczne.
Jedyną lokalizacją, w której profiler CLR może bezpiecznie wywołać kod zarządzany, jest treść wspólnego języka pośredniego (CIL) metody. Zalecaną praktyką modyfikowania treści CIL jest użycie metod rekompilacji JIT w interfejsie ICorProfilerCallback4 .
Można również użyć starszych metod instrumentacji do modyfikowania CIL. Przed ukończeniem kompilacji funkcji just in time (JIT) profiler może wstawić wywołania zarządzane w treści metody CIL, a następnie skompilować ją JIT (zobacz metodę ICorProfilerInfo::GetILFunctionBody ). Ta technika może zostać pomyślnie użyta do instrumentacji selektywnej kodu zarządzanego lub zebrania statystyk i danych wydajności dotyczących trybu JIT.
Alternatywnie profiler kodu może wstawić natywne haki w treści CIL każdej funkcji zarządzanej, która wywołuje kod niezarządzany. Ta technika może służyć do instrumentacji i pokrycia. Na przykład profiler kodu może wstawić zaczepienia instrumentacji po każdym bloku CIL, aby upewnić się, że blok został wykonany. Modyfikacja treści CIL metody jest bardzo delikatną operacją i należy wziąć pod uwagę wiele czynników.
Profilowanie niezarządzanych kodów
Interfejs API profilowania środowiska uruchomieniowego języka wspólnego (CLR) zapewnia minimalną obsługę profilowania niezarządzanego kodu. Oferowane są następujące funkcje:
Wyliczenie łańcuchów stosu. Ta funkcja umożliwia profilerowi kodu określenie granicy między kodem zarządzanym a niezarządzanym kodem.
Określenie, czy łańcuch stosu odpowiada kodowi zarządzanemu, czy kodowi natywnym.
W programie .NET Framework w wersji 1.0 i 1.1 te metody są dostępne za pośrednictwem podzestawu w procesie interfejsu API debugowania CLR. Są one zdefiniowane w pliku CorDebug.idl.
W programie .NET Framework 2.0 lub nowszym można użyć metody ICorProfilerInfo2::D oStackSnapshot dla tej funkcji.
Korzystanie z modelu COM
Mimo że interfejsy profilowania są zdefiniowane jako interfejsy COM, środowisko uruchomieniowe języka wspólnego (CLR) w rzeczywistości nie inicjuje modelu COM do używania tych interfejsów. Przyczyną jest unikanie konieczności ustawiania modelu wątkowania przy użyciu funkcji CoInitialize , zanim zarządzana aplikacja miała szansę określić żądany model wątków. Podobnie sam profiler nie powinien wywoływać CoInitialize
elementu , ponieważ może wybrać model wątkowy, który jest niezgodny z profilem aplikacji i może spowodować niepowodzenie aplikacji.
Stosy wywołań
Interfejs API profilowania udostępnia dwa sposoby uzyskiwania stosów wywołań: metodę migawki stosu, która umożliwia rozrzedżone zbieranie stosów wywołań oraz metodę stosu w tle, która śledzi stos wywołań w każdej chwili.
Migawka stosu
Migawka stosu to ślad stosu wątku natychmiast w czasie. Interfejs API profilowania obsługuje śledzenie funkcji zarządzanych na stosie, ale pozostawia śledzenie niezarządzanych funkcji do własnego przewodnika stosu profilera.
Aby uzyskać więcej informacji na temat programowania profilera w celu chodzenia zarządzanych stosów, zobacz metody ICorProfilerInfo2::D oStackSnapshot w tym zestawie dokumentacji oraz Profiler Stack Walking w programie .NET Framework 2.0: Basics and Beyond.
Stos w tle
Użycie metody migawki zbyt często może szybko utworzyć problem z wydajnością. Jeśli chcesz często wykonywać ślady stosu, profiler powinien zamiast tego utworzyć stos w tle przy użyciu wywołań zwrotnych wyjątków FunctionEnter2, FunctionLeave2, FunctionTailcall2 i ICorProfilerCallback2 . Stos w tle jest zawsze aktualny i można go szybko skopiować do magazynu za każdym razem, gdy jest wymagana migawka stosu.
Stos w tle może uzyskiwać argumenty funkcji, zwracane wartości i informacje o wystąpieniach ogólnych. Te informacje są dostępne tylko za pośrednictwem stosu w tle i mogą być uzyskiwane po przekazaniu kontrolki do funkcji. Jednak te informacje mogą nie być dostępne później podczas uruchamiania funkcji.
Wywołania zwrotne i głębokość stosu
Wywołania zwrotne profilera mogą być wystawiane w bardzo ograniczonych okolicznościach, a przepełnienie stosu w wywołaniu zwrotnym profilera doprowadzi do natychmiastowego zakończenia procesu. Profiler powinien mieć pewność, że używasz jak najmniejszego stosu w odpowiedzi na wywołania zwrotne. Jeśli profiler jest przeznaczony do użycia z procesami niezawodnymi w stosunku do przepełnienia stosu, sam profiler powinien również unikać wyzwalania przepełnienia stosu.
Tematy pokrewne
Nazwa | opis |
---|---|
Konfigurowanie środowiska profilowania | W tym artykule wyjaśniono, jak zainicjować profilera, ustawić powiadomienia o zdarzeniach i profilować usługę systemu Windows. |
Interfejsy profilowania | Opisuje niezarządzane interfejsy używane przez interfejs API profilowania. |
Profilowanie statycznych funkcji globalnych | Opisuje niezarządzane globalne funkcje statyczne używane przez interfejs API profilowania. |
Profilowanie — wyliczenia | Opisuje niezarządzane wyliczenia używane przez interfejs API profilowania. |
Profiling — struktury | Opisuje niezarządzane struktury używane przez interfejs API profilowania. |