Profilowanie służy temu, by znaleść “kulę u nogi” Twojego programu, czyli te funkcje, które wykonują się najdłużej i warte są przepisania/zoptymalizowania. Linux posiada do tego celu narzędzie – gprof. Dzięki niemu dowiesz się, ile czasu Twój program spędza w danej funkcji oraz ile razy ją wywołuje.
Trzy kroki do znalezienia kuli u nogi
Żeby sprofilować program narzędziem gprof musimy wykonać trzy proste polecenia:
> g++ -pg -o program program.cpp // kompilujemy w trybie profilowania > ./program // uruchamiamy program > gprof -p -b program // wyświetlamy wyniki profilowania
Flagi -p -b oznaczją, że chcemy raport płaski – tylko wywołania poszczególnych funkcji.
Zobaczmy, co dzieje się w kolejnych krokach.
1. Kompilujemy i linkujemy w trybie profilowania
Bardzo prosty krok – wystarczy do flag kompilacji g++ dołożyć dodatkową flagę -pg:
> g++ -pg -o program program.cpp
Dzięki temu w trakcie kompilacji nasz program zostanie automatycznie wzbogacony o instrukcje, które przeanalizują działanie jego funkcji, zbiorą dane statystyczne i zapiszą je do pliku.
(Jeśli nie czujesz się zbyt pewnie w temacie g++, zobacz wpis pt. Kompilowanie C++ pod Linuxem)
2. Uruchamiamy program
Najzwyczajniej w świecie odpalamy program:
> ./program
Program działa i zbiera dane statystyczne na temat wywoływanych funkcji, a gdy kończy – zapisuje je do pliku gmon.out. Pamiętaj, że dostaniesz informacje tylko o tych funkcjach, które rzeczywiście zostały wykonane! To znaczy, że jeśli na początku funkcji main wywołasz funkcję exit – nie otrzymasz zbyt obszernych danych na temat Twojego kodu ;)
Druga ważna rzecz – informacje statystyczne zapisywane są do pliku jedynie przy normalnym zakończeniu działania programu – tzn. przy wywołaniu funkcji exit lub zakończeniu funkcji main. Jeśli program zostanie zakończony awaryjnie np. w wyniku rzucenia wyjątku, lub “zabity” z terminala – informacje przepadną.
I ostatnia ważna rzecz. Rejestrowany jest wyłącznie czas, w którym aplikacja czynnie wykonuje jakieś operacje. Przykładowo: czas, w którym aplikacja liczy silnię z miliona – zostanie policzony. Ale czas, w którym aplikacja czeka na dane lub przydzielenie zasobów – nie będzie policzony. Np. funkcja cin.get() będzie policzona za 0.0 sekund, nie ważne, jak długo będzie czekała na wciśnięcie klawisza.
3. Oglądamy statystyki programu
Do wyświetlania statystyk służy program gprof, który czyta dane z pliku binarnego gmon.out i wypluwa je na standardowe wyjście:
> gprof -p -b program
Flagi określające wygląd raportu:
- -b – bez opisów kolumn (inaczej – tryb kompaktowy)
- -q – tylko graf wywołań (pokazuje jakie funkcje wywołała dana funkcja)
- -p – tylko płaski profil (pokazuje czas trwania i liczbę wywołań poszczególnych funkcj)
Czytanie wyników
Przykładowy raport może wyglądać tak (płaski profil):
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
33.34 0.02 0.02 7208 0.00 0.00 open
16.67 0.03 0.01 244 0.04 0.12 offtime
16.67 0.04 0.01 8 1.25 1.25 memccpy
16.67 0.05 0.01 7 1.43 1.43 write
16.67 0.06 0.01 mcount
0.00 0.06 0.00 1 0.00 50.00 main
0.00 0.06 0.00 1 0.00 50.00 report
.
Widać tutaj, że funkcja open została wywołana 7208 razy, co w sumie zajęło 0.02 sekundy, czyli 33.34% całkowitego czasu wykonkania aplikacji. Całkiem to proste, prawda :)
A oto znaczenie poszczególnych kolumn:
- pierwsza linijka mówi o tym, że czas próbkowany jest co 0,01 sekundy. Oznacza to w praktyce, że sens mają tylko wartości powyżej 0,1s – dając błąd pomiaru około 10%
- % time – jaki procent czasu program spędził w tej funkcji
- cumulative seconds – suma czasów spędzonych w tej funkcji i poprzednich (tych wyżej w kolumnie)
- self seconds – ile sekund program spędził w tej funkcji
- calls – ile razy funkcja została wywołana
- self ms/call – ile średnio milisekund zajęło jedno wywołanie funkcji
- total ms/call – ile średnio milisekund zajęło wywołanie tej funkcji + funkcji przez nią wywoływanych (stos wywołań)
- name – nazwa funkcji
Podsumowanie
Wiesz już dość by zacząć profilować swoje aplikacje. Dzięki narzędziu gprof możesz łatwo zorientować się, które funkcje najbardziej spowalniają program i są warte zoptymalizowania. Następnym razem pokażę Ci, jak wykorzystać profilowanie na prostym, ale zaskakującym przykładzie…