Systemy profilowania - profilowanie kodu

//Post przeznaczony raczej dla początkujących programistów

01. W inżynierii oprogramowania, profilowanie(ang. program profiling, software profiling lub po prostu - profiling) jest formą dynamicznej analizy programu. Jest to sposób analizy zachowania oprogramowania w oparciu o dane zebrane w czasie wykonywania badanego obiektu(programu, lub jakiejś jego części np. funkcji etc.). Zwykle celem tej analizy jest określenie, które części programu można optymalizować - zwiększyć ogólną szybkość, zmniejszyć zapotrzebowanie na pamięć, a czasem jedno i drugie.


Profiler(kodu) to narzędzie do analizy wydajności, które najczęściej bada jedynie - częstotliwość wywoływania funkcji oraz czas trwania danej partii kodu.[*1]

Początkujący koder mówi: Tworzę programy do testowania wydajności, do analizy czasu trwania funkcji czy liczby wywołań określonego kodu. Gdyby znał omawiane w tym tekście to stwierdziłby raczej że stworzył swój własny system profilowania. Piszemy kilka programów profilujących by za kilka lat dowiedzieć się że to co zrobiliśmy nosi właśnie taką nazwę. Dzieje się tak z wielu powodów, być może jednego z poniższych:
  • W necie znajdziemy na ten temat wiele ale raczej w języku angielskim,
  • Po co optymalizować? Komputery są takie wydajne i wypasione, niech coś robią.
  • Po co optymalizować? Tu cytat: "Najszybszy algorytm można zastąpić innym, prawie tak samo szybkim i znacznie bardziej zrozumiałym."[1]
  • Pierwsza zasada optymalizowania programu - "Nie rób tego."
  • Druga zasada optymalizowania programu - tylko dla ekspertów - "Nie rób tego jeszcze."[2]
Programy należy optymalizować, to nie ulega wątpliwości. Choć do najłatwiejszych zadań to nie należy. Czasami bezmyślne pisanie kodu wydłuży go kilkukrotnie. Każdy większy projekt trzeba profilować. Poniższy cytat całkiem nieźle do tego przekonuje i co ważniejsze nie kłamie - "Zwykle mniej niż 4% programu zużywa więcej niż połowę czasu jego wykonania."[3] 

Z optymalizacją nie należy przesadzać(czyt. 1000 linijek kodu by zyskać 100ms), na tym procesie muszą zyskać obie strony - programiści(a raczej deweloperzy) i użytkownicy. Warto zapamiętać tę zasadę - KISS(ang. Keep it simple, stupid) - Nie komplikuj, głupku.

02. Wykaz wybranych narzędzi do analizy wydajności(warto zajrzeć do źródła po pełny spis).[*2]

Multi C/C++ Delphi Java JavaScript .NET PHP
Gprof, Linux Trace Toolkit CodeAnalyst, Dtrace, GlowCode Sampling-Profiler Jprofiler, Netbeans Profiler AjaxView CLR Profiler, NProf Dbg

03. Wprowadzenie do profilowania z GNU gprof(The GNU Profiler)[*3]
Proling allows you to learn where your program spent its time and which functions called which other functions while it was executing. This information can show you which pieces of your program are slower than you expected, and might be candidates for rewriting to make your program execute faster. It can also tell you which functions are being called more or less often than you expected. This may help you spot bugs that had otherwise been unnoticed. Since the profiler uses information collected during the actual execution of your program, it can be used on programs that are too large or too complex to analyze by reading the source.
Powyżej cytat z dzieła(podręcznika gprof'a) Stallman'a i Fenlason'a, mówiący ogólnie o profilowaniu. Fragment, mniej więcej zawiera to co napisałem na samej górze ale autorzy przytaczają też zalety procesu profilowania, czyli np: wykrywanie błędów, znajdywanie nazbyt „gorących miejsc” programu i łatwiejsza analiza oprogramowania o zbyt obszernym kodzie źródłowym.

Proces profilowania(w przypadku GNU gprof) można podzielić na kilka kroków:
  • Kompilowanie, linkowanie programu z włączoną opcją profilowania.
  • Uruchomienie programu w celu wygenerowania pliku z danymi profilowania.
  • Analiza programem gprof pliku z danymi profilowania.
Poniżej bardziej szczegółowy opis każdego kroku wraz z przykładami.

A. Aby przygotować kod źródłowy do profilowania należy dodać opcję -pg wraz z komendą kompilacji lub linkowania. Dodatkowy argument -pg wygląda tak samo dla obu operacji np:

    cc -g - c test.c add.c -pg
    gcc -o test test.c -pg
    g++ test.cpp -pg

Ważne: Opcja -pg musi być częścią zarówno kompilacji jak i linkowania. W przeciwnym wypadku gprof wyrzuci błąd, wyglądający mnie więcej tak:
gprof: gmon.out file is missing call-graph data
B. Profilowany program przed samym zakończeniem stworzy plik z danymi profilowania; pod nazwą 'gmon.out'. Jeżeli plik taki w aktualnym katalogu już się znajdował, zostanie on nadpisany nowymi danymi. W obecnej wersji GNU gprof'a nie ma możliwości by zapisać ten plik pod inną nazwą. Aby właściwie wygenerować plik 'gmon.out', program musi zakończyć się normalnie: powracając z funkcji main lub przez wywołanie funkcji exit. Kończenie programu nisko-poziomową funkcją _exit jest w tym wypadku nieprawidłowe. W tym kroku najważniejsza jest pamięć o nim.

C. Mając plik 'gmon.out' należy uruchomić gprof'a by zinterpretował wyniki. Gprof może zwrócić wyniki na wiele sposobów, standardowo wyświetla flat profile(profil płaski) i call graph(graf wywołań). Wzór polecenia wygląda następująco:

gprof [dodatkowe opcje] [nazwa pliku wykonywalnego]
      [nazwa pliku\ów z danymi profilowania]
Ta komenda ma jedną wadę - wyrzuci wyniki prosto do okna konsolki. Aby zapisać wyniki w dowolnym miejscu, dodajemy na końcu:
     > [nazwa_pliku]
Przykład:
     gprof test.exe gmon.out > profilowanie.txt

gmon.out można w tym wypadku pominąć gdyż jest to standardowa nazwa wymaganego pliku. W systemach linux'owych cała procedura wygląda jeszcze prościej. Mianowicie można wszystko pominąć a gprof interpretuje pliki z standardowymi nazwami - a.out i gmon.out. W Windows'ie należałoby jeszcze w trakcie kompilacji podać dodatkowy parametr zmieniający nazwę:

g++ -o a.out test.cpp -pg     -kompilacja
a.out                         -uruchomienie pliku wykonywalnego
gprof        -proces interpretacji, zwyczajnie odpalamy gprof'a

Gprof zinterpretował wyniki, my musimy zrobić to samo:

The Flat Profile - Profil Płaski
The flat profile shows the total amount of time your program spent executing each function.

Główne przeznaczenie profilu płaskiego, doskonale wyjaśnia powyższe zdanie z podręcznika grpof'a. Znaczy mniej więcej tyle co: Profil płaski służy do mierzenia czasu, każdej wykonanej funkcji programu, na tle całości. Nie używane funkcje, pomimo iż zawarte w programie nie będą brane pod uwagę chyba że dopiszemy opcję -z.
Przykładowy profil płaski z podręcznika gprof'a:

% time cumulative seconds self seconds calls self ms/call total 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

Funkcje są sortowane wg. schematu: %time, następnie calls i na końcu alfabetycznie po nazwach.
Co znaczą poszczególne pola? No..., to:

%time - procent czasu spędzonego w danej funkcji względem czasu trwania całej aplikacji. W sumie powinno dać 100%(a to niespodzianka)
cumulative seconds - czas wykonywania funkcji i wszystkich procedur nad nią(w tabeli). Dla funkcji najkrótszych, wskaźnik ten jest najwyższy.
self seconds - czas wykonywania danej funkcji(w ms).
calls - ilość wywołań danej funkcji; pole w niektórych przypadkach może być puste.
self ms/call - średni czas spędzony w danej funkcji na wywołanie.
total ms/call - średni czas spędzony w danej funkcji razem z czasem trwania funkcji potomnych.
name - nazwa danej funkcji.

Przydatne opcje:
-z     wspomniane wcześniej, wypisywanie "nieważnych funkcji",
-p    włączenie profilu płaskiego,
-P    wyłączenie profilu płaskiego,
-b    wyłączenie wyświetlania znaczeń poszczególnych pól(czyli np. %time), bardzo użyteczne. Listing z tą opcją jest znacznie krótszy i czytelniejszy.

The Call Graph - Graf Wywołań
The call graph shows how much time was spent in each function and its children. From this information, you can find functions that, while they themselves may not have used much time, called other functions that did use unusual amounts of time.

Czyli: Graf wywołań ułatwia doszukanie się funkcji, które same w sobie nie są czasochłonne ale  wywołują inne(funkcje-dzieci) zjadające niezwykłe ilości czasu.

Przykładowy graf wywołań z podręcznika gprof'a:

granularity: each sample hit covers 2 byte(s) for 20.00% of 0.05 seconds
index %time self children called name
spontaneous
[1] 100.0 0.00 0.05 start [1]
0.00 0.05 1/1 main [2]
0.00 0.00 1/2 on_exit [28]
0.00 0.00 1/1 exit [59]
-----------------------------------------------------------------------------------
0.00 0.05 1/1 start [1]
[2] 100.0 0.00 0.05 1 main [2]
0.00 0.05 1/1 report [3]
-----------------------------------------------------------------------------------
Wiersze wypełnione kreskami(minusami) dzielą tabelę na wpisy(entries), jeden na funkcję. Wpisy są ułożone według czasu spędzonego w funkcji i pod-funkcjach.

Poszczególne pola w przypadku grafu wywołań są bardzo podobne do profilu płaskiego i oznaczają:

index - oznacza liczbę porządkową.

%time - procent czasu spędzonego w danej funkcji i jej potomnych(funkcji) na tle czasu trwania całej aplikacji.
self - czas spędzony w danej funkcji. Określa tę samą wartość co pole self seconds w profilu płaskim.
children - czas spędzony na funkcjach potomnych(tych wywołanych z funkcji badanej).
called - ilość wywołań danej funkcji. W przypadku gdy funkcja jest rekurencyjna(rekursywna) gprof podaj dwie liczby dzielone znakiem '+'. Pierwsza liczba jest równa zwykłym wywołaniom, druga wywołaniom rekurencyjnym.
name - nazwa funkcji z powtórzonym numerem porządkowym po niej(nazwie).

Przydatne opcje:
-q     włączenie grafu wywołań,
-Q    wyłączenie grafu wywołań,
-b     wyłączenie wyświetlania znaczeń poszczególnych pól(czyli np. %time), bardzo użyteczne. Listing z tą opcją jest znacznie krótszy i czytelniejszy.

04. Krótkie wprowadzenie do profilowania z GNU gcov. [*4]

Gcov jest to profiler dołączony do kompilatora GCC. Bada programy pod kątem „pokrycia kodu” czyli ile razy dana linijka została wywołana. Bardzo przydatne w procesie optymalizacji, choć należy uważać bo linijka linijce nierówna. Np. funkcja sqrt jest bardziej czasochłonna niż funkcja sumująca dwie liczby.

Chcąc profilować aplikację z gcov'em należy jej kod źródłowy skompilować z dwoma specjalnymi opcjami:
-fprofile-arcs -ftest-coverage
Dalej należy zwyczajnie odpalić skompilowany w ten sposób program.
Ostatecznie musimy zmusić gcov'a do analizy, wpisując po prostu:
gcov kod_źródłowy

W bieżącym katalogu zostanie utworzony plik z profilowaniem, zakończony rozszerzeniem .gcov.
(Jest to zwykły plik tekstowy więc można go przejrzeć zwykłym notepad'em)

Przykład:

g++ zrodlo.cpp -fprofile-arcs -ftest-coverage
a.exe
gcov zrodlo.cpp

Utworzony plik z profilowaniem, w tym wypadku - zrodlo.cpp.gcov. Wygląda mniej więcej tak:

        -:    0:Source:P1.C
        -:    0:Graph:P1.gcno
        -:    0:Data:P1.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <cstdio>
        -:    2:
        -:    3:int prime(int n)
function _Z5primei called 999 returned 100% blocks executed 100%
      999:    4:{
      999:    5:  int i;
        -:    6:
    78190:    7:  for (i = 2; i<n; i++)
    78022:    8:  if (n % i == 0)
      831:    9:  return 0;
      168:   10:  return 1;
        -:   11:}
        -:   12:
        -:   13:int main()
function main called 1 returned 100% blocks executed 100%
        1:   14:{
        1:   15:  int i, n;
        1:   16:  n = 1000;
     1000:   17:  for (i = 2; i <= n; i++)
      999:   18:  if (prime(i))
      168:   19:  printf("%d\n", i);
        1:   20:}

Krótkie wprowadzenie dobiegło końca. 
W wypadku kompilatora GCC warto się też zainteresować programem GNU lcov, który generuje to samo co gcov ale plikiem wynikowym jest .html.
Programów tego typu jest całe mnóstwo, warto jednak samemu stworzyć swój system profilowania.
-----
[1]Douglas W. Jones
[2]Michael Jackson Systems Ltd.
[3]Don Knuth Stanford University
Źródła                                                                                                                                                     
[*1]http://en.wikipedia.org/wiki/Profiling_(computer_programming)
[*2]http://en.wikipedia.org/wiki/List_of_performance_analysis_tools
[*3]GNU gprof_manual by Jay Fenlason & Richard Stallman
[*4]http://gcc.gnu.org/onlinedocs/gcc/Gcov.html
More Programming Pearls Confessions of a Coder by Jon Bentley

Wirtualne urządzenie z procesorem AMD

Komentarze

Prześlij komentarz