Dokładny czas na wielu platformach

2010-06-26 23:54

Gry i inne podobne aplikacje wymagają precyzyjnego pomiaru czasu, aby realizować obliczenia związane z fizyką, animacją, itp. Jak niemal wszystko, API potrzebne do tego celu jest różne w zależności od platformy, czyli (głównie) systemu operacyjnego. Dzisiaj chciałbym pokazać, jak wygląda to na dwóch najpopularniejszych systemach: Windows i POSIX (a więc między innymi na wszelkiego typu Linuksach).

Interfejs programistyczny systemu spod znaku okienek udostępnia dwie funkcje od dokładnego mierzenia czasu. Są to QueryPerformanceFrequency i QueryPerformanceCounter. Tę pierwszą wywołuje się tylko raz, a jej wynikiem jest częstotliwość wewnętrznego systemowego zegara, który służy do precyzyjnego pomiaru upływającego czasu. Wyrażana jest ona w liczbie “tyknięć” na sekundę i na dzisiejszym sprzęcie może być bardzo duża, bo liczona w (kilku(nastu/dziesięciu)) milionach.
Druga funkcja jest używana nieporównywalnie częściej, gdyż to ona zwraca aktualną wartość zegara, czyli liczbę jego “tyknięć”. Stąd wynika, że obliczenie tej wartości w sekundach wymaga podzielenia rezultatu QPC przez uzyskaną wcześniej częstotliwość:

  1. LARGE_INTEGER freq, counter;
  2. QueryPerformanceFrequency (&freq);
  3. // ...
  4. QueryPerformanceCounter (&counter);
  5. double secs = counter.QuadPart / (double)freq.QuadPart;

Używana tu unia LARGE_INTERGER to nic innego, jak zwykła liczba 64-bitowa.

W systemach POSIX-owych rzecz jest odrobinę prostsza, jako że tutaj dokładność zegara jest ściśle ustalona. Funkcja gettimeofday (z nagłówka sys/time.h) zwraca rezultat z precyzją mikrosekundową w postaci struktury timeval:

  1. struct timeval tv;
  2. gettimeofday (&tv, 0);
  3. double sec = tv.tv_sec + tv.tv_usec / 1000000.0;

Część odpowiadającą niepełnym sekundom (pole tv_usec) trzeba więc podzielić przez milion.

Chcąc mieć bardziej uniwersalne rozwiązanie, możemy napisać klasę opakowującą zegar z implementacją kontrolowaną docelową platformą, na którą kompilujemy:

  1. class Clock
  2. {
  3.     #ifdef _WINDOWS
  4.         LARGE_INTEGER freq;
  5.         public: Clock() { QueryPerformanceFrequence (&freq); }
  6.     #endif
  7.  
  8.     double GetTicks() const
  9.     {
  10.         #ifdef _WINDOWS
  11.             LARGE_INTEGER counter;
  12.             QueryPerformanceCounter (&counter);
  13.             return counter.QuadPart / (double)freq.QuadPart;
  14.         #else
  15.             struct timeval tv; gettimeofday (&tv, 0);
  16.             return tv.tv_sec + tv.tv_usec / 1000000.0;
  17.         #endif
  18.     }
  19. };

Można by na koniec zapytać jeszcze, co tak naprawdę znaczy zwracana wartość zegara. Kiedy wynosi ona zero?… Otóż wbrew pozorom odpowiedź na to pytanie nie jest istotna, bo w praktyce interesuje nas tylko interwał czasowy, tj. różnica między dwoma wskazaniami timera. To na jej podstawie liczymy zmianę w prędkościach obiektów, klatkach animacji czy w końcu niezbędnie konieczną do wyświetlenia wartość FPS :)

Tags: , , , ,
Author: Xion, posted under Programming »


4 comments for post “Dokładny czas na wielu platformach”.
  1. Anonymous:
    January 9th, 2013 o 2:12

    Gdy komputer jest długo uruchomiony QueryPerformanceCounter może osiągnąć wartości zbyt duże, żeby można je było przekonwertować na double bez zaokrąglania. W moim przypadku dokładność mierzonego czasu spadła do 1/8 sekundy.

    Żeby temu zapobiec zapamiętuję stan licznika podczas uruchamiania programu i zmniejszam go o tę wartość przy każdym sprawdzeniu.
    return (counter.QuadPart – startValue) / (double)freq.QuadPart;

  2. Xion:
    January 9th, 2013 o 12:21

    O, interesujące. Jakiego rzędu uptime jest potrzebny żeby dokładność spadła aż tak bardzo?

    Zastanawiam się też czy konwersje na double mogą zmniejszyć rzeczywistą dokładność QPC – nawet przy użyciu twojego tricku – poniżej ~18ms (dokładność GetTickCount) przy odpowiednio długim uptime’ie. Da się to dokładnie wyliczyć znając trochę szczegółów IEEE1394, ale ciekawy jest chociaż rząd wielkości. Czy jest to na przykład więcej niż ~49 dni po których licznik GetTickCount się przekręca? :)

  3. Anonymous:
    January 27th, 2013 o 13:37

    Zrestartowałem komputer, więc musiałem poczekać, aż licznik znowu wzrośnie. Zazwyczaj tylko hibernuję system. Nie resetuje to licznika, przez co może on osiągnąć bardzo duże wartości.

    Aktualna wartość to około 3.5*10^12, co przy QueryPerformanceFrequency równym 2240527 daje uptime około 18 dni.

    Bez użycia mojej sztuczki uzyskuję pokaz slajdów, zamiast płynnej animacji. Wydaję mi się (subiektywnie), że dokładność spadła już poniżej 100ms.

  4. Mateusz Semegen:
    April 7th, 2013 o 23:28

    Wiem, że post mega stary, ale masz małą literówkę: QueryPerformanceFrequency. :)

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.