Precyzyjne odmierzanie czasu na Uniksach – portowanie rozwiązania dla Windows

0

W swoim projekcie korzystam z funkcji z Windows API do precyzyjnego odmierzania czasu. Mowa o dwóch funkcjach: QueryPerformanceFrequency i QueryPerformanceCounter. Wartości zwracane przez te funkcje przechowuję w zmiennych typu Int64.

Tuż po uruchomieniu programu, pobieram i zapamiętuję rozdzielczość sprzętowego countera, za pomocą QueryPerformanceFrequency. W trakcie działania programu, wielokrotnie pobieram bieżący stan licznika – za pomocą funkcji QueryPerformanceCounter – i na podstawie tych wartości odmierzać czas. Funkcja ta za każdym razem zwraca liczbę większą w stosunku do poprzedniego wywołania (ciąg rosnący, a przynajmniej niemalejący) i na tym mi zależy.


Kod projektu chciałbym teraz przystosować do Uniksów (różne Linuksy i macOS). Dużo poczytałem na ten temat, znalazłem nawet odpowiedniki dla wymienionych funkcji – odpowiednio clock_getres i clock_gettime. Znalazłem też przykłady użycia tych funkcji – wykorzystywane są w module timestamps.helpers.pas. Problem w tym, że nie za bardzo wiem jak to u siebie wykorzystać…

Nie rozumiem co konkretnie zawierają pola struktury typu timespec, jak wartości tych pól poprawnie przerobić na jedną liczbę typu Int64, których stałych użyć w wywołaniach tych funkcji, jak to poprzeliczać itd.


Pomógłby ktoś z tym? Byłbym wdzięczny.

0

Na stronie głównej zobaczyłem tytuł wątku, zobaczyłem też w jakiej jest kategorii i od razu widziałem, kto jest autorem :)

P.S. Nie myliłem się.

0

Skorzystałem z dokumentacji typu timespec dla C++ i wychodzi na to, że pole tv_sec przechowuje liczbę sekund, jaka upłynęła od początku jakiegoś tam zdarzenia (załóżmy, że od uruchomienia peceta), a tv_nsec przechowuje dodatkowo liczbę nanosekund, jaka upłynęła od pełnej sekundy. Czyli zegar pracuje ze stałą dokładnością co do nanosekundy.

To powoduje, że jego rozdzielczości pobierać nie muszę – jest znana i wynosi 1.000.000.000 (miliard). Skoro pełne sekundy dostajemy w polu tv_sec, to wystarczy je pomnożyć przez miliard i dodać do tego wartość pola tv_nsec. W ten sposób uzyskamy liczbę nanosekund, jaka upłynęła od początkowego zdarzenia i taka dokładność mi jak najbardziej pasuje.

Czyli z funkcji clock_getres korzystać nie trzeba, a clock_gettime należy użyć w ten sposób:

var
  Counter: TTimeSpec;
  Nanoseconds: Int64;
begin
  clock_gettime(CLOCK_MONOTONIC_RAW, @Counter);
  Nanoseconds := Int64(Counter.tv_sec) * 1000000000 + Counter.tv_nsec;
end;

Wzór jest podobny do tego zawartego w kodzie, na który nakierował mnie @Paweł Dmitruk.

Tyle że tamten kod jest mylący, bo liczba sekund z pola tv_sec mnożona jest przez tysiąc (nie wiadomo po co), a liczba nanosekund z pola tv_nsec jest dzielona przez milion (aby dopasować wartość do uprzednio pomnożonej liczby sekund, co znów myli).

Jeśli liczbę sekund pomnoży się przez miliard i doda do tego liczbę nanosekund (czyli tak jak podałem wyżej), to otrzyma się dokładnie taką samą wartość, tyle że bez żadnego zmylania operacjami z czterech liter.


Podsumowując, wygląda na to, że metody mojego zegara powinny wyglądać tak:

function TClock.GetHardwareCounterFrequency(): Int64;
begin
  {$IFNDEF WINDOWS}
  Result := 1000000000;
  {$ELSE}
  Result := 0;
  QueryPerformanceFrequency(Result);
  {$ENDIF}
end;

function TClock.GetHardwareCounterValue(): Int64;
{$IFNDEF WINDOWS}
var
  Counter: TTimeSpec;
begin
  clock_gettime(CLOCK_MONOTONIC_RAW, @Counter);
  Result := Int64(Counter.tv_sec) * 1000000000 + Counter.tv_nsec;
{$ELSE}
begin
  Result := 0;
  QueryPerformanceCounter(Result);
{$ENDIF}
end;

W skrócie, bez sprawdzania rezultatów użytych funkcji. Teoretycznie rzutowanie tv_sec na Int64 nie jest potrzebne, bo typ time_t to alias cint64, a ten jest aliasem standardowego Int64.

1 użytkowników online, w tym zalogowanych: 0, gości: 1