Czy gettimeofday () ma rozdzielczość mikrosekundową?

97

Przenoszę grę, która została pierwotnie napisana dla Win32 API, na Linuksa (cóż, przenoszę port OS X portu Win32 na Linuksa).

Mam realizowane QueryPerformanceCounterprzez podanie uSeconds ponieważ proces uruchamiania:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

To, w połączeniu z QueryPerformanceFrequency()podaniem stałej 1000000 jako częstotliwości, działa dobrze na moim komputerze , dając mi 64-bitową zmienną, która zawiera się uSecondsod momentu uruchomienia programu.

Czy to jest przenośne? Nie chcę odkryć, że działa inaczej, jeśli jądro zostało skompilowane w określony sposób lub coś w tym rodzaju. Jednak nie przeszkadza mi to, że jest nieprzenośny na coś innego niż Linux.

Bernarda
źródło

Odpowiedzi:

57

Może. Ale masz większe problemy. gettimeofday()może powodować nieprawidłowe czasy, jeśli w systemie są procesy, które zmieniają licznik czasu (np. ntpd). Jednak na "normalnym" Linuksie uważam, że rozdzielczośćgettimeofday() wynosi 10us. W konsekwencji może przeskakiwać do przodu i do tyłu oraz w czasie w oparciu o procesy uruchomione w systemie. To skutecznie daje odpowiedź na twoje pytanie nie.

Powinieneś sprawdzić clock_gettime(CLOCK_MONOTONIC)odstępy czasu. Cierpi na kilka mniej problemów z powodu takich rzeczy, jak systemy wielordzeniowe i zewnętrzne ustawienia zegara.

Przyjrzyj się także clock_getres()funkcji.

Louis Brandy
źródło
1
clock_gettime jest obecny tylko w najnowszym Linuksie. inne systemy mają tylko gettimeofday ()
vitaly.v.ch
3
@ vitaly.v.ch to POSIX, więc nie jest to tylko Linux i „nowicjusz”? nawet dystrybucje „Enterprise”, takie jak Red Hat Enterprise Linux, są oparte na wersji 2.6.18, która ma clock_gettime, więc nie, niezbyt nowy ... mówisz o NAPRAWDĘ FREAKING STARE jądra WTF, masz na myśli?
Spudd86
clock_gettime został dołączony do POSIX w 2001 roku. O ile wiem, obecnie clock_gettime () zaimplementowano w Linuksie 2.6 i qnx. ale linux 2.4 jest obecnie używany w wielu systemach produkcyjnych.
vitaly.v.ch
Został wprowadzony w 2001 roku, ale nie był obowiązkowy aż do POSIX 2008.
R .. GitHub STOP HELPING ICE
2
Z Linux FAQ dla lock_gettime (patrz odpowiedź Davida Schlosnagle'a) "CLOCK_MONOTONIC ... jest dostosowywana częstotliwością przez NTP poprzez adjtimex (). W przyszłości (wciąż próbuję pobrać poprawkę) będzie CLOCK_MONOTONIC_RAW, który nie będzie zostaną w ogóle zmodyfikowane i będą miały liniową korelację z licznikami sprzętowymi. " Nie sądzę, by zegar _RAW kiedykolwiek znalazł się w jądrze (chyba że został przemianowany na _HR, ale moje badania sugerują, że wysiłki również zostały zaniechane).
Tony Delroy
41

Wysoka rozdzielczość, niskie taktowanie w przypadku procesorów Intel

Jeśli korzystasz ze sprzętu firmy Intel, oto jak odczytać licznik instrukcji procesora w czasie rzeczywistym. Podaje liczbę cykli procesora wykonanych od momentu uruchomienia procesora. Jest to prawdopodobnie najdrobniejszy licznik, jaki można uzyskać do pomiaru wydajności.

Zauważ, że jest to liczba cykli procesora. W Linuksie możesz pobrać prędkość procesora z / proc / cpuinfo i podzielić, aby uzyskać liczbę sekund. Konwersja tego na podwójną jest całkiem przydatna.

Kiedy uruchomię to na moim pudełku, dostaję

11867927879484732
11867927879692217
it took this long to call printf: 207485

Oto przewodnik dla programistów firmy Intel, który zawiera mnóstwo szczegółów.

#include <stdio.h>
#include <stdint.h>

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}
Mark Harrison
źródło
11
Zauważ, że TSC może nie zawsze być zsynchronizowany między rdzeniami, może zatrzymać lub zmienić swoją częstotliwość, gdy procesor wchodzi w tryby niższego poboru mocy (i nie możesz tego wiedzieć) i ogólnie nie zawsze jest niezawodny. Jądro jest w stanie wykryć, kiedy jest niezawodne, wykryć inne alternatywy, takie jak zegar HPET i ACPI PM, i automatycznie wybrać najlepszą. Dobrym pomysłem jest zawsze używanie jądra do mierzenia czasu, chyba że jesteś naprawdę pewien, że TSC jest stabilne i monotoniczne.
CesarB
12
TSC na platformach Core i wyższych Intel jest synchronizowany na wielu procesorach i zwiększa się ze stałą częstotliwością, niezależnie od stanów zarządzania energią. Zobacz Podręcznik programisty Intel, t. Sekcja 18.10. Jednak szybkość, z jaką zwiększa się licznik, nie jest taka sama, jak częstotliwość procesora. TSC zwiększa się przy „maksymalnej rozdzielczej częstotliwości platformy, która jest równa iloczynowi skalowalnej częstotliwości magistrali i maksymalnego współczynnika rozdzielczej magistrali” Intel Software Developer's Manual, t. Sekcja 18.18.5. Otrzymujesz te wartości z rejestrów specyficznych dla modelu procesora (MSR).
sstock
7
Możesz uzyskać skalowalną częstotliwość magistrali i maksymalny współczynnik rozwiązanej magistrali, odpytując rejestry specyficzne dla modelu procesora (MSR) w następujący sposób: Skalowalna częstotliwość magistrali == MSR_FSB_FREQ [2: 0] id 0xCD, Maksymalny rozwiązany współczynnik magistrali == MSR_PLATFORM_ID [12: 8] identyfikator 0x17. Aby zinterpretować wartości rejestrów, zapoznaj się z Intel SDM Vol.3 Dodatek B.1. Możesz użyć msr-tools w systemie Linux do przeszukiwania rejestrów. kernel.org/pub/linux/utils/cpu/msr-tools
sstock
1
Czy twój kod nie powinien być CPUIDponownie użyty po pierwszej RDTSCinstrukcji i przed wykonaniem kodu, który jest testowany? W przeciwnym razie, co zatrzyma wykonanie kodu porównawczego przed / równolegle z pierwszym RDTSC, aw konsekwencji niedostateczną reprezentację w RDTSCdelcie?
Tony Delroy
18

@Bernard:

Muszę przyznać, że większość twojego przykładu przeszła mi przez głowę. Jednak kompiluje się i wydaje się działać. Czy jest to bezpieczne dla systemów SMP lub SpeedStep?

To dobre pytanie ... Myślę, że kod jest w porządku. Z praktycznego punktu widzenia używamy go w mojej firmie każdego dnia i pracujemy na dość szerokiej gamie pudełek, wszystko od 2 do 8 rdzeni. Oczywiście YMMV itp., Ale wydaje się, że jest to niezawodna i niewielka (ponieważ nie powoduje przełączenia kontekstu na przestrzeń systemową) metoda pomiaru czasu.

Ogólnie, jak to działa:

  • zadeklaruj blok kodu jako asembler (i nietrwały, więc optymalizator pozostawi go w spokoju).
  • wykonać instrukcję CPUID. Oprócz pobierania niektórych informacji o procesorze (z którymi nic nie robimy) synchronizuje bufor wykonania procesora, aby nie wpływać na czasy wykonania poza kolejnością.
  • wykonuje wykonanie rdtsc (odczyt znacznika czasu). To pobiera liczbę cykli maszynowych wykonanych od zresetowania procesora. Jest to wartość 64-bitowa, więc przy obecnych prędkościach procesora będzie ona zawijana mniej więcej co 194 lata. Co ciekawe, w oryginalnym źródle Pentium zauważają, że zawija się co około 5800 lat.
  • ostatnie kilka wierszy przechowuje wartości z rejestrów do zmiennych hi i lo i umieszcza je w 64-bitowej wartości zwracanej.

Szczegółowe uwagi:

  • wykonanie poza kolejnością może powodować niepoprawne wyniki, dlatego wykonujemy instrukcję "cpuid", która oprócz podania pewnych informacji o procesorze synchronizuje również wykonanie instrukcji poza kolejnością.

  • Większość systemów operacyjnych synchronizuje liczniki na procesorach podczas uruchamiania, więc odpowiedź jest dobra w ciągu kilku nanosekund.

  • Komentarz dotyczący hibernacji jest prawdopodobnie prawdziwy, ale w praktyce prawdopodobnie nie przejmujesz się taktowaniem poza granicami hibernacji.

  • w odniesieniu do szybkości: nowsze procesory Intel kompensują zmiany prędkości i zwracają skorygowaną liczbę. Zrobiłem szybkie skanowanie niektórych pudełek w naszej sieci i znalazłem tylko jedno pudełko, w którym go nie było: Pentium 3 ze starym serwerem bazy danych. (to są skrzynki linuxowe, więc sprawdziłem: grep constant_tsc / proc / cpuinfo)

  • Nie jestem pewien co do procesorów AMD, jesteśmy przede wszystkim sklepem Intela, chociaż wiem, że niektórzy z naszych guru od systemów niskiego poziomu przeprowadzili ocenę AMD.

Mam nadzieję, że to zaspokoi Twoją ciekawość, jest to interesujący i (IMHO) niedostatecznie zbadany obszar programowania. Wiesz, kiedy Jeff i Joel rozmawiali o tym, czy programista powinien znać C? Krzyczałem na nich, „hej, zapomnijcie o tych wysokopoziomowych rzeczach C ... asembler jest tym, czego powinniście się nauczyć, jeśli chcecie wiedzieć, co robi komputer!”.

Mark Harrison
źródło
1
... Ludzie od jądra próbowali przez jakiś czas skłonić ludzi do zaprzestania używania rdtsc ... i generalnie unikają używania go w jądrze, ponieważ jest po prostu zawodny.
Spudd86
1
Dla porównania, pytanie, które zadałem (w osobnej odpowiedzi - przed komentarzami) brzmiało: „Muszę przyznać, że większość twojego przykładu przeszła mi prosto przez głowę. Kompiluje się i wydaje się, że działa. Czy jest to bezpieczne dla Systemy SMP czy SpeedStep? ”
Bernard,
9

Więc wyraźnie mówi o mikrosekundach, ale mówi, że rozdzielczość zegara systemowego jest nieokreślona. Przypuszczam, że rozdzielczość w tym kontekście oznacza, jak najmniejsza kwota kiedykolwiek zostanie zwiększona?

Struktura danych jest zdefiniowana jako jednostka miary w mikrosekundach, ale to nie znaczy, że zegar lub system operacyjny są w stanie dokładnie to zmierzyć.

Jak sugerowali inni, gettimeofday()jest złe, ponieważ ustawienie czasu może spowodować przesunięcie zegara i zepsuć obliczenia. clock_gettime(CLOCK_MONOTONIC)jest tym, czego chcesz, i clock_getres()powie ci precyzję twojego zegara.

Joe Shaw
źródło
Więc co dzieje się w Twoim kodzie, gdy gettimeofday () przeskakuje do przodu lub do tyłu z czasem letnim?
mpez0
3
clock_gettime jest obecny tylko w najnowszym Linuksie. inne systemy mają tylko gettimeofday ()
vitaly.v.ch
8

Rzeczywista rozdzielczość gettimeofday () zależy od architektury sprzętowej. Procesory Intela, a także maszyny SPARC oferują liczniki czasu o wysokiej rozdzielczości, które mierzą mikrosekundy. Inne architektury sprzętowe powracają do licznika czasu systemu, który jest zwykle ustawiony na 100 Hz. W takich przypadkach rozdzielczość czasowa będzie mniej dokładna.

Otrzymałem tę odpowiedź z Pomiaru czasu i timerów o wysokiej rozdzielczości, część I.

Kodowanie bez komentarzy
źródło
6

Ta odpowiedź wspomina o problemach z regulacją zegara. Zarówno problemy z gwarantowaniem jednostek ticka, jak i problemy z dostosowywaniem czasu są rozwiązywane w C ++ 11 za pomocą rozszerzenia<chrono> biblioteką.

std::chrono::steady_clockGwarantuje się, że zegar nie będzie regulowany, a ponadto będzie postępował ze stałą szybkością w stosunku do czasu rzeczywistego, więc technologie takie jak SpeedStep nie mogą na to wpływać.

Możesz zdobyć jednostki bezpieczne, przechodząc na jedną ze std::chrono::durationspecjalizacji, na przykład std::chrono::microseconds. W przypadku tego typu nie ma dwuznaczności co do jednostek używanych przez wartość ticka. Należy jednak pamiętać, że zegar niekoniecznie ma taką rozdzielczość. Możesz przekonwertować czas trwania na attosekundy bez posiadania tak dokładnego zegara.

bames53
źródło
4

Z mojego doświadczenia iz tego, co przeczytałem w internecie, odpowiedź brzmi „Nie”, nie jest to gwarantowane. Zależy to od szybkości procesora, systemu operacyjnego, rodzaju Linuksa itp.

Kodowanie bez komentarzy
źródło
3

Odczyt RDTSC nie jest wiarygodny w systemach SMP, ponieważ każdy procesor utrzymuje swój własny licznik i nie ma gwarancji, że każdy licznik będzie zsynchronizowany z innym procesorem.

Mogę zasugerować spróbować clock_gettime(CLOCK_REALTIME). Podręcznik posix wskazuje, że powinno to być wdrożone we wszystkich zgodnych systemach. Może podać liczbę nanosekund, ale prawdopodobnie będziesz chciał sprawdzić clock_getres(CLOCK_REALTIME)w systemie, jaka jest rzeczywista rozdzielczość.

Doug
źródło
clock_getres(CLOCK_REALTIME)nie da prawdziwej rozdzielczości. Zawsze zwraca "1 ns" (jedną nanosekundę), gdy dostępne są hrtimery, sprawdź include/linux/hrtimer.hplik define HIGH_RES_NSEC 1(więcej na stackoverflow.com/a/23044075/196561 )
osgx