Profilowanie programów i odpluskwianie pamięci

Profilowanie dynamiczna analiza programu, która dokonuje pomiaru złożoności czasowej lub/i pamięciowej w czasie jego działania

Debbuger pamięci narzędzie do poszukiwania wycieków pamięci i innych błędów związanych z niedozwolonym dostępęm do dynamicznie przydzielanych obszarów pamięci (np. buffer overflow)

Profilowanie

Pomaga zlokalizować funkcje lub fragmenty kodu na które należy zwrócić uwagę przy optymalizacji szybkości działania programu

Czy warto optymalizować?

Funkcje wołane tysiące razy

Funkcje najbardziej kosztowne obliczeniowo

Profilowanie pomaga też często wykrywać błędy i lokalizować wady konstrukcji programu (np. niespodziewane wywołania funkcji lub brak wywołań)

Techniki profilowania

instrumentacja, wstrzykiwanie do kodu instrukcji monitorujących (np. logi, liczniki wywołań, itp.)

profilowanie oparte na zdarzeniach

statystyczna, próbkowanie rejestru Program counter

symulacja, instruction set simulator (ISS), uruchamianie programu w symulowanym środowisku wirtualnym

Instrumentacja

Dodawane są instrukcje potrzebne do uzyskania informacji o wydajności aplikacji

liczniki instrukcji i innych zdarzeń (performance counters)

zastępowanie funkcji alokujących pamięć

opakowanie wywołań funkcji odpowiednimi mechanizmami zliczającymi

Pozwala na:

precyzyjne śledzenie wykonywanych instrukcji

pomiar czasu wywołań w czasie działania programu

debbugowanie i wyłapywanie sytuacji wyjątkowych

logging

Duży wpływ na wydajność programu profilowanego, plik wykonywalny jest o wiele większy

Może to też zaburzać pomiar, tzw Heisenbug

Rodzaje instrumentacji

Ręczna, dokonywana przez programistę (logi, assercje)

Automatyczna modyfikacja kodu (Parasoft, Insure++)

Instrumentacja kodu pośredniego, dodawana do assembly lub po dessaemblacji

Z wykorzystaniem kompilatorów, symbole debuggera umieszczene w kodzie maszynowym, np. gcc -pg dla gprof, Quantify

Binarna, instrumentacja dodawana do kodu maszynowego (ATOM)

W czasie działania (runtime): instrumentacja w momencie uruchamiania, program działa pod nadzorem narzędzia profilującego (Valgrind, Pin, DynamicRIO), kod modyfikowany w czasie wykonywania (DynInst)

Event-based profile

profilowanie oparte na przechwytywanych zdarzeniach, np. tworzenie obiektów, wywołanie metod, odpalenie wątków

zakładanie haków (hooks)

.NET dołącza agenta profilującego jako serwer COM do CLR (Profiling API).

Środowisko uruchomieniowe dostarcza informacji do agenta o wywołaniach metod, utworzeniu obiektów, itd.

Agent profilujący potrafi podmienić bytecode profilowanego programu w odpowiedni sposób.

Matody statystyczne

Próbkowanie rejestru procesora wskazującego pozycję programu (Program counter) za pomocą przerwań systemu operacyjnego z pewną częstością.

Metoda w małym stopniu lub wcale nie ingeruje w kod i wykonanie programu

Mniej dokładny pomiar ale pozwala na śledzenie działania programu profilowanego niemal z pełną szybkością.

Wyniki są przybliżeniem statystycznym i obarczone są błedem pomiarowym

Rozdzielczość pomiaru (ziernistośc danych): zależy od częstości próbkowania. Szybko wykonujące się funkcje bedą niezauważalne

Sposoby raportowania

profil: ogólne statystyki zaobserwowanych zdarzeń, np. zliczenie wywołań poszczególnych instrukcji, wystarczające dla programów sekwencyjnych (jednowątkowych). Raport zazwyczaj zawiera zestawienie kodu z liczbą wywołań więc jego rozmiar jest liniowy względem liczby linii kodu.

ślad (trace), ciąg zaobserwowanych zdarzeń, uwzględnia zależności czasowe także dla programów równoległych. Rozmiar liniowy względem długości ścieżki kodu (instruction path length), z tego względu często niepraktyczny w zastosowaniach.

ciągły monitoring na maszynie wirtualnej. Pozwala uruchamiać śledzenie ścieżek w dowolnym momencie, śledzenie na żywo zmian w wydajności, mechanizmy usypiania procesów asynchronicznych w celu dokładniejszego obejrzenia równoległych procesów

Wynik działania

płaski (flat) - sumaryczne czasy wywołań poszczególnych instrukcji lub funkcji

graf wywołań (call graph) - uwzględniają kolejność wywołań i pozwalają oddzielić czas działania funkcji wywołującej od funkcji badanej i funkcji potomnych

zależny od wejścia (input sensitive), uzależniają wynik profilowania od rozmiaru i/lub wartości danych wejściowych. Dodają niejako dodatkowy wymiar do profili płaskich i grafów wywołań

Call graph

relacje wywołań pomiędzy funkcjami programu

cykle oznaczają rekujencję

brak informacji o kontekście

Źródło: wikipedia.org

Narzędzia do profilowania

GNU gprof, Valgrind (Unix/Linux)

Visual Studio Profiler

NetBeans Profiler, Eclipse Profiler Plugin

inne : nprof, CLR Profiler, RotateRight Zoom, pin, JProfiler (commercial), TAU Performance System, slimtune (.Net), CMT++ (Complexity Measures Tool), AQtime (.Net, Java, commercial),

dotTrace

List of performance analysis tools

gprof

Analiza wydajności programów dla Unix/Linux skompilowanych w GCC

Połączenie instrumentacji z próbkowaniem

Instrumentacja w czasie kompilacji
gcc -pg source_files

Funkcja monitorujące mcount() umieszczana przed każdym wywołaniem funkcji

Próbkowanie tworzy histogram wywołań badając stan licznika programu (program counter) co 0.01 s

Płaski profil + graf wywołań

Użycie gprof

Instrumentacja
gcc -pg program.c -o program

Uruchomienie programu generuje plik gmon.out

Analiza wyników
grpof gmon.out

gprof2dot pozwala wizualizować graf wywołań

Przykład

void func2(void) { int i = 0; for(;i<0xffffff;i++); return; } void func1(void) { int i = 0; for(;i<0xfffffff;i++); func2(); return; } int main(void) { int i = 0; for(;i<0xfffffff;i++); func1(); for(i=0 ; i< 3 ; i++) func2(); return 0; }

Profil płaski

gprof a.out gmon.out

Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
44.92 0.80 0.80 1 799.52 850.12 func1
44.92 1.60 0.80 main
11.37 1.80 0.20 4 50.60 50.60 func2

Graf wywołań

granularity: each sample hit covers 2 byte(s) for 0.56% of 1.80 seconds
index % time self children called name
<spontaneous>
[1] 100.0 0.80 1.00 main [1]
0.80 0.05 1/1 func1 [2]
0.15 0.00 3/4 func2 [3]
-----------------------------------------------
0.80 0.05 1/1 main [1]
[2] 47.2 0.80 0.05 1 func1 [2]
0.05 0.00 1/4 func2 [3]
-----------------------------------------------
0.05 0.00 1/4 func1 [2]
0.15 0.00 3/4 main [1]
[3] 11.2 0.20 0.00 4 func2 [3]
-----------------------------------------------

Inny przykład (program make): http://linuxgazette.net/100/misc/vinayak/overall-profile.txt sample.png

Ograniczenia

próbkowanie rejestrów procesora daje statystyczne przybliżenie (obarczone jest błędem)

pomiar czasu dotyczy tylko funkcji programu, nie uwzględnia czasu wywołań systemowych

duże spowolnienie działania aplikacji (w granicach 30% - 260%)

tylko GCC i kilka innych kompilatorów

problemy z cyklami w grafie wywołań (gprof nie analizuje całego grafu)

Analiza pamięci (Memory debugging tools)

Valgrind, KCacheGrind (Linux/Unix, Android)

Visual Leak Detector (Visual C++)

Eclipse Memory Analyzer (Java)

inne: IBM Rational Purify, Insure++, TotalView, Dmalloc, memwatch, YAMD, mpatrol

dotMemory

Lista debbugerów pamięci

Valgrind - brama do Valhalli

Początkowo debbuger pamięci, obecnie rozbudowany framework do instrumentacji binarnej

Linux/Unix, Android, GPL v2

Maszyna wirtualna - program jest tłumaczony (dekompilowany) do neutralnej dla procesora formy pośredniej (Intermediate Representation (IR)). Następnie dokonywana jest instrumentacja i wynik zamieniany jest ponownie na kod wykonywalny.

Narzędzia valgrind

Memcheck (domyślnie) - błedy związane z przydziałem pamięci wynamicznej

Cachegrind - cache profiler, wizualizacja KCacheGrind

Callgrind - cachegrind + graf wywołań, wizualizacja KCacheGrind

Helgrind i DRD - analiza wielowątkowa, wykrywanie np. wyścigów (race condition), zakleszczeń, ...

Massif - profiler sterty + massif-visualizer

Sgcheck - wykrywanie błędów pamięci nieuwzględnianych przez memcheck (tablice na stercie i globalne)

BBV - ekstrapolacja wydajności za pomocą symulacji

None - uruchomienie programu bez analizy

Uruchomienie

valgrind [opcje valgrind] program [opcje programu]

Przykład:

valgrind --tool=memcheck ls -l

Nie wymaga dostępu do źródeł programu ale kompilacja z symbolami debbugowania pozwala powiązać wynik z kodem:

gcc -g kod_programu

Memcheck

Memcheck - analiza pamięci dynamicznej

Instrumentacja przechwytuje (podmienia) wszystkie wywołania funkcji alokujących i zwalniających pamięć pozwalając na badanie przekroczenia granic alokowanej pamięci

validity - dostępnośc pamięci i porawaność instrukcji, bity V, dla pamięci i CPU

addresability - adresowalność pamięci, wskaźniki do zaalokowanego obszaru pamięci, bity A dla bajtów pamięci, narzut ~25% wielkości programu

Dokładność: dostęp pamięci na poziomie bajta, inzicjalizacje zmiennych na poziomie bita

Program wykonywany 20-30 razy wolniej i zajmuje o wiele więcej pamięci

Memcheck: wykrywane błędy

Dostęp do niezaalokowanego obszaru pamięci

Przekroczenie granic zaalokowanej pamięci

Próba dostępu do pamięci po jej zwolnieniu

Wycieki pamięci

Wykorzystanie niezainicjowanych zmiennych

Błedy przy kopiowania buforów, np. memcpy

Przykład

#include <stdlib.h> void f(void) { int* x = malloc(10 * sizeof(int)); x[10] = 0; // problem 1: heap block overrun } // problem 2: memory leak -- x not freed int main(void) { f(); return 0; }

Przykład

gcc -pg -g mem1.c
valgrind --leak-check=yes a.out

==23065== Invalid write of size 4
==23065== at 0x400670: f (mem1.c:8)
==23065== by 0x400685: main (mem1.c:13)
==23065== Address 0x51fd778 is 0 bytes after a block of size 40 alloc'd
==23065== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==23065== by 0x400663: f (mem1.c:7)
==23065== by 0x400685: main (mem1.c:13)
==23065==
==23065==
==23065== HEAP SUMMARY:
==23065== in use at exit: 40 bytes in 1 blocks
==23065== total heap usage: 2 allocs, 1 frees, 1,780 bytes allocated
==23065==
==23065== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==23065== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==23065== by 0x400663: f (mem1.c:7)
==23065== by 0x400685: main (mem1.c:13)
==23065==

Wycieki pamięci

Still reachable - pamięć jest osiągalna

Definitely lost - brakuje wskaźnika do zaalokowanej pamięci

Indirectly lost - istnieją wskaźniki do tej pamięci ale i tak nie ma tam dostępu, np. drzewo bez korzenia

Possibly lost - może być problem dla zarządzanego kodu (interior-pointers)

==23065== LEAK SUMMARY:
==23065== definitely lost: 40 bytes in 1 blocks
==23065== indirectly lost: 0 bytes in 0 blocks
==23065== possibly lost: 0 bytes in 0 blocks
==23065== still reachable: 0 bytes in 0 blocks
==23065== suppressed: 0 bytes in 0 blocks

Cachegrind i Callgrind

Cachegrind symuluje szczegółowo cache I1, D1 i L2. Pozwala identyfikować poszczególne wywołania linii kodu i odwołania do pamięci.

Podsumowania wyników dla funkcji, modułów, całego programu

Działa dla programów napisanych w dowolnym języku

Około 20-100x woniej

Callgrind uzupełnia analizę Cachegrind o graf wywołań

KCachegrind - wizualizacja danych z cachegrind i callgrind

KCachegrind

valgring --toll=callgrind program
kcachegrind

Profilowanie kodu w VS2013

Sampling zbiera dane statystyczne dotyczące wykonywanej aplikacji próbkując obciążenie CPU i stan stosu

Instrumentation - pomiary wywołania poszczególnych funkcji

Concurrency aplikacje wielowątkowe

.NET memory użycie pamięci i garbage collection

Tier interaction zawołania ADO.NET do baz danych SQL Serwera

Performance Session

Okno Perforamnce Explorer: konfiguracja sesji, uruchamianie testów, porównywanie raportów

Performance Session - konfiguracja analizy + raporty

Targets - cel analizy: projekt, działający proces, plik wykonywalny

Reports - pliki zawierające informacje zebrane podczas analizy

Attach/Detach - podłączenie profilowania do działającego procesu

Wymagania

Uprawnienia administracyjne - wymagane przez metody próbkowania (sampling) oraz mające dostęp do wywołań systemowych

Skompilowanie aplikacji do wersji Release

Debug symbols files (.pdb) - m.in. definicja nazw funkcji systemowych

Visual Studio Professional/Premium/Ultimate

Windows 8, Windows Server 2012, aplikacje Windows Store - wymagają innych technik profilowania, nie wszystkie wymienione tu są wspierane w VS

Raport

Hot Path najaktywniejsza ścieżka drzewa wywołań funkcji

Lista funkcji wykonujących najwięcej pracy

Inclusive - łącznie dla całej funkcji, i

Exclusive - po odjęciu funkcji wołanych z wnętrza analizowanej funkcji

Sampling

W stałych odstępach czasu pobiera z CPU stan stosu (informacje o autualnie wykonywanych funkcjach)

Próbkowanie: domyślnie co 10M cykli (0.01 s dla 1GHz), inne możliwe zdarzenia do próbkowania:

błędy stronicowania

wywołania systemowe

performance counters (CPU counters)

w czasie próbkowania zwiekszany jest licznik wywołań (inclusive samples) dla wszystkich funkcji ze stosu

Funkcja aktualnie wykonywana otrzymuje zwiększony licznik exclusive sample oraz inclusive sample

Inclusive samples - całkowita liczba próbek zebranych dla danej funnkcji

Exclusive samples - liczba próbek pobranych w czasie gdy funkcja była wykonywana

Instrumentation

Pomiar czasu wykonania funkcji, linii kodu i poszczególnych instrukcji

Wstrzykiwany kod na początku i końcu badanych funkcji, mierzony jest czas pomiędzy kolejnymi zdarzeniami

Sprawdzane jest czy pomiędzy zdarzeniami system dokonywał innych operacji (np. odczyt z dysku, komunikacja miedzy wątkami, ...)

Dla każdego zarejestrowanego interwału odtwarzany jest stos wywołań w celu określenia funkcji potomnych

Elapsed Inclusive - czas wykoanania danej funkcji

Elapsed Exclisive - czas z pominięciem czasu funkcji potomnych

Application Inlcusive - czas wywołań w czasie których nie nastąpiło wywołanie systemowe

Application Exclusive - jak wyżej, z pominięciem czasów funkcji potomnych

Szczegóły analizy funkcji

Error List

Okno Error List zawiera ostrzeżenia i wskazówki dotyczące efektywności aplikacji

Performance Rules: błędy, ostrzeżenia, informacje

Filtrowanie wyników

Żródło: http://msdn.microsoft.com

CPU and Windows Counters

VS Profiler pozwala zbierać informacje o zdarzeniach systemu (windows counters, events) i procesora (CPU counters)

Windows counters - zdarzenia dostarczane przez infrastrukture diagnostyczną systemu, w zależności od konfiguracji nie muszą wystepować. Dostarczają informacji o wydajności OS, aplikacji, usług i sterowaników

CPU counters - zdarzenia związane ze sprzętem zliczane przez CPU

Portable Performance counter

Platform Events - specyficzne dla danej architektury, nie przenośne

Memory Allocation and Object Lifetime

Zbiera informacje o rozmiarze i liczbie alokowanych obiektów i obiektów niszczoncyh w garbage collector

Zachowuje stan stosu dla każdego takiego zdarzenia

Inclusive Sample Count - liczba zdarzeń alokacji obiektu gdy funkcja znajdowała się na stosie

Exclusive Sample Count - liczba zdarzeń alokacji obiektu gdy funkcja była wywoływana (była na szczycie stosu)

Object Lifetime view - ilość i rozmiar zaalokowanych obiektów oraz informacja o generacji GC w której zostały zniszczone

Raport

Całkowita zajęcośc pamięci

Funkcje, które zaalokowały najwięcej pamięci

Lista typów obiektów zajmujących najwięcej miejsca

Lista typów posiadających najwięcej instancji

Porównywanie raportów

Podsumowanie

Pozwala wskazać fragmenty kodu, które należy rozpatrzyć pod względem optymalizacji

Dynamiczna analiza: sprawdzane tylko te elementy które zostały wykoane

Profilowanie jest kosztowne czasowo i pamięciowo

Profilowanie jest obarczone błędami, każda technika ma swoje ograniczenia

Więcej informacji

Beginners Guide to Performance Profiling

Analyzing Application Performance by Using Profiling Tools

Find Application Bottlenecks with Visual Studio Profiler by Hari Pulapaka and Boris Vidolov

Profilowanie - Profiling (computer programming)

http://en.wikipedia.org/wiki/List_of_performance_analysis_tools

Profiling programs using gprof by Vinayak Hegde (Linux Gazette)

Valgrind Documentation