Dla odmiany dzisiaj napisałem niewielką, ale bardzo wiele mówiącą o każdym kodzie rzecz – czyli profiler. Już wyjaśniam, że jest to moduł służący do kontrolowania wydajności: dzięki odpowiednio umieszczonym wywołaniom można przy jego pomocy określić, ile czasu zajmuje wykonywanie poszczególnych części programu.
Zasadniczą częścią jest tu oczywiście jakiś sposób pomiaru czasu. Dawniej, pisząc moduł profilujący, przygotowałem go tak, by oprócz czasu faktycznego w (mili/nano)sekundach podawał też ilość cykli procesora przypadających na daną operację. Na procesorach x86 można to zrobić przy pomocy rozkazu RDTSC. Obecnie porzuciłem tę koncepcję z kilku powodów, przy czym najbardziej prozaiczny jest ten, że… nigdy mi się taka funkcjonalność nie przydała :) Poza tym obecnie wartość zwracana przez wspomnianą instrukcję (a jest to liczba cykli procesora od momentu ‘resetu’) jest mało wiarygodna, jako że zdarzenia w rodzaju wstrzymywania czy hibernacji systemu bardzo lubią ją resetować. I wreszcie: ciężko jest przecież optymalizować kod pod kątem pojedynczych cykli procesora, jeśli pisze się głównie w języku wysokiego poziomu.
Wspomniałem, że to nie jest mój pierwszy profiler. W istocie, tak naprawdę dokonałem teraz zwyczajnego przepisania go (czy raczej drobnego przerobienia jego kodu) tak, by pasował do nowej wersji mojej biblioteki kodów wszelakich. (Bo cały czas bronię się, żeby nazywać ją silnikiem :)). W sumie więc nie jest to jakieś wielkie osiągnięcie, ale przynajmniej dowodzi tego, że kod napisany parę lat wcześniej nie musi od razu iść do kosza. Aczkolwiek jeśli chodzi o tę poprzednią wersję biblioteki, to raczej nic ciekawego już w niej nie znajdę :]
Wypadałoby teraz powiedzieć co nieco o tym, w jaki sposób tego starego-nowego profilera się używa. Otóż jest to całkiem proste. Bazując na jednym z rozdziałów pierwszego tomu Perełek programowania gier zorganizowałem pomiar czasu hierarchicznie. Można zatem korzystać z tego, że mniejsze operacje są częścią większych i tak je profilować – na przykład:
Dzięki temu można sprawdzać nie tylko maksymalny, minimalny i średni czas generowania całej ramki gry, ale też te same czasy osobno np. dla samego renderowania. Dalej można by jeszcze dodać statystyki dotyczącego tego, ile czasu (minimalnie, maksymalnie i średnio) zajmują procentowo czynności “mniejsze” względem “większych”.
Ale to już wymaga dopisania od podstaw, więc w przeciwieństwie do recyklingu starego kodu nie byłoby takie proste :)
Fajnie że następny kolega dołączył do grona blogujących. Życzę powodzenia w prowadzeniu bloga jak i swojego projektu programistycznego!
Do profilera możnaby dopisać, jeśli lubisz koncepcję RAII, klasę która w konstruktorze sama wywoływałaby Begin a w destruktorze End, co pozwoliłoby pisać o tak:
{
ProfilerHelper cokolwiek(&Profiler);
…
}
Podobna koncepcja jest właśnie w Perełkach, ale ja tam nie lubię gdy coś robi się za plecami programisty :) Po prostu łatwiej śledzić Begin/End niż zasięgi wyznaczane nawiasami klamrowymi.
Akurat przymierzałem się do napisania własnego profilera – strasznie modne to się stało ;)
Fajowski blog!
Nie wiem dlaczego, ale tytul przeczytalem tak: “O profilowaniu, cyckach i kodzie z odzysku”. Cos ze mna nie tak… :P
Swego czasu napisałem hierarchicznego loggera, w obrębie każdej funkcji były akcje, które oczywiście mogły się powieść lub nie. Łatwo by było do tego dodać profiling ;)
Dobra koncepcja.