2008-08-01 26 views
80

Tak więc uruchamiam grę, która została oryginalnie napisana dla Win32 API, do Linuksa (cóż, portowanie portu OS X portu Win32 na Linuksa). I wprowadziły QueryPerformanceCounter przez podanie uSeconds ponieważ proces uruchamiania:Czy gettimeofday() ma gwarantowaną mikrosekundową rozdzielczość?

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() podając stałą 1000000 jak częstotliwość, działa dobrze na moim komputerze, dając mi zmiennej 64 bitowej, który zawiera uSeconds od czasu uruchomienia programu. Tak więc jest przenośny? Nie chcę odkryć, że działa inaczej, jeśli kernel został skompilowany w określony sposób lub coś w tym stylu. Nie przeszkadza mi to, że jest nieprzenośny na coś innego niż Linux.

Odpowiedz

53

Może. Ale masz większe problemy. gettimeofday() może powodować niepoprawne ustawienia czasu w przypadku procesów w systemie, które zmieniają timer (np. Ntpd). Na "normalnym" Linuksie uważam jednak, że rozdzielczość gettimeofday() wynosi 10us. Może przeskakiwać do przodu i do tyłu oraz do czasu, w konsekwencji, w oparciu o procesy uruchomione w systemie. To skutecznie sprawia, że ​​odpowiedź na twoje pytanie nie.

Powinieneś przejrzeć clock_gettime(CLOCK_MONOTONIC) dla interwałów czasowych. Cierpi na kilka mniej problemów ze względu na systemy wielordzeniowe i zewnętrzne ustawienia zegara.

Zobacz także funkcję clock_getres().

+1

clock_gettime występuje tylko w najnowszym systemie Linux. inny system ma tylko gettimeofday() –

+3

@ vitaly.v.ch to POSIX, więc nie jest to tylko Linux i "nowicjusz"? nawet dystrybucje "Enterprise", takie jak Red Hat Enterprise Linux, oparte są na wersji 2.6.18, która ma clock_gettime, więc nie, niezbyt nowa.(data strony man w RHEL to 2004-marzec-12, więc jest już od jakiegoś czasu) chyba że mówisz o NAPRAWDĘ FREAKING OLD Jądra WTF masz na myśli? – Spudd86

+0

clock_gettime został włączony do POSIX w 2001 roku. O ile mi wiadomo obecnie clock_gettime() zaimplementowany w Linuksie 2.6 i qnx. ale Linux 2.4 jest obecnie używany w wielu systemach produkcyjnych. –

4

Z mojego doświadczenia iz tego, co przeczytałem przez Internet, odpowiedź brzmi "Nie", nie jest to gwarantowane. Zależy to od szybkości procesora, systemu operacyjnego, smaku Linuksa, itp.

8

Rzeczywista rozdzielczość gettimeofday() zależy od architektury sprzętowej. Procesory Intel oraz SPARC oferują zegary o wysokiej rozdzielczości, które mierzą mikrosekundy. Inne architektury sprzętowe wracają do zegara systemowego, który zwykle ma wartość 100 Hz. W takich przypadkach rozdzielczość czasowa będzie mniej dokładna.

Zdobyłem tę odpowiedź od High Resolution Time Measurement and Timers, Part I

39

High Resolution, Overhead niskiej częstotliwości dla procesorem Intel

jeśli jesteś na sprzęcie Intel, oto jak do zapoznania się z procesora w czasie rzeczywistym licznik instrukcji . Pokaże liczbę cykli procesora wykonanych od momentu uruchomienia procesora. Jest to prawdopodobnie najdoskonalszy licznik, jaki można uzyskać w celu pomiaru wydajności.

Należy zauważyć, że jest to liczba cykli procesora. Na Linuksie możesz uzyskać prędkość procesora z/proc/cpuinfo i podzielić, aby uzyskać liczbę sekund. Przekształcenie tego w podwójne jest bardzo przydatne.

Gdy uruchomię to na moim polu, mam

11867927879484732 
11867927879692217 
it took this long to call printf: 207485 

Oto Intel developer's guide który daje 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); 
} 
+11

Należy zauważyć, że TSC może nie zawsze być zsynchronizowany między rdzeniami, może zatrzymać się lub zmienić jego częstotliwość, gdy procesor wchodzi w tryb niskiego poboru mocy (i nie masz możliwości, aby to zrobić), a na ogół nie zawsze jest niezawodny. Jądro może wykryć, kiedy jest niezawodny, wykryć inne opcje, takie jak HPET i zegar ACPI PM, i automatycznie wybrać najlepszy z nich. Dobrym pomysłem jest zawsze używać jądra do pomiaru czasu, chyba że jesteś naprawdę pewien, że TSC jest stabilny i monotoniczny. – CesarB

+11

TSC na platformach Core i powyżej Intel jest zsynchronizowany na wielu procesorach * i * przyrostach ze stałą częstotliwością niezależnie od stanów zarządzania energią. Patrz Podręcznik programisty firmy Intel, cz. 3 Rozdział 18.10. Jednak szybkość przyrostu licznika * nie * jest taka sama jak częstotliwość procesora. TSC zwiększa się przy "maksymalnej rozdzielczości 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, wol. 3 Sekcja 18.18.5. Otrzymujesz te wartości z rejestrów specyficznych dla modelu procesora (MSR). – sstock

+7

Można uzyskać skalowalną częstotliwość magistrali i maksymalny współczynnik proporcji magistrali, wysyłając zapytanie do rejestrów modelu (MSR) procesora w następujący sposób: Skalowalna częstotliwość magistrali == MSR_FSB_FREQ [2: 0] id 0xCD, Maksymalny współczynnik podziału magistrali == MSR_PLATFORM_ID [12 : 8] id 0x17. Skonsultuj się z Intel SDM Vol.3 Dodatek B.1, aby zinterpretować wartości rejestru. Możesz użyć msr-tools w systemie Linux, aby wysłać zapytanie do rejestrów. http://www.kernel.org/pub/linux/utils/cpu/msr-tools/ – sstock

9

Tak mówi mikrosekund wyraźnie, ale mówi, że rozdzielczość zegara systemowego jest nieokreślona.Przypuszczam, że rozwiązanie w tym kontekście oznacza jak najmniejszą kwotę, jaką kiedykolwiek zwiększy?

Struktura danych jest określona jako mikrosekunda jako jednostka miary, ale to nie znaczy, że zegar lub system operacyjny jest rzeczywiście w stanie to dokładnie zmierzyć.

Podobnie jak inne osoby zasugerowały, gettimeofday() jest złe, ponieważ ustawienie czasu może spowodować przekrzywienie zegara i zrzucić obliczenia. clock_gettime(CLOCK_MONOTONIC) jest tym, czego potrzebujesz, a clock_getres() powie Ci precyzję twojego zegara.

+0

Co dzieje się w kodzie, gdy gettimeofday() przeskakuje do przodu lub do tyłu z uwzględnieniem czasu letniego? – mpez0

+3

clock_gettime występuje tylko w najnowszym systemie Linux. inny system ma tylko gettimeofday() –

+0

@ mpez0 to nie jest – Spudd86

18

@Bernard:

muszę przyznać, że większość przykład poszedł prosto na głowę. 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 codziennie, i uruchamiamy na dość szerokiej gamie pudeł, wszystko od 2-8 rdzeni. Oczywiście, YMMV, itp., Ale wydaje się być niezawodny i nisko napięty (ponieważ nie zmienia kontekstu w przestrzeń systemową) metoda odmierzania czasu.

Ogólnie, jak to działa to:

  • zadeklarować blok kodu być assembler (i lotny, więc optymalizator go zostawić w spokoju).
  • Wykonaj instrukcję CPUID. Oprócz uzyskania pewnych informacji o procesorze, (z którym nie robimy nic), synchronizuje bufor wykonawczy procesora , tak aby na czasy nie wpływało wykonywanie poza kolejnością.
  • Wykonaj wykonanie polecenia rdtsc (read timestamp). Spowoduje to pobranie liczby cykli maszynowych wykonanych od momentu zresetowania procesora. Jest to 64-bitowa wartość , więc przy obecnych prędkościach procesora będzie się ona owijać co około 194 lata. Co ciekawe, w oryginalnym referencji Pentium zauważają, że owija się wokół każdego co najmniej jednego tysiąca lat.
  • ostatnie kilka linii przechowuje wartości z rejestrów w zmiennych hi i lo, a następnie umieszcza je w 64-bitowej wartości zwracanej.

Szczególne uwagi:

  • wykonywanie poza kolejnością może spowodować nieprawidłowe wyniki, więc wykonujemy „CPUID” Instrukcja które oprócz daje pewne informacje o CPU synchronizuje także każde wykonanie instrukcji poza kolejnością.

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

  • Komentarz do hibernacji jest prawdopodobnie prawdziwy, ale w praktyce prawdopodobnie nie zależy Ci na taktowaniu przez granice hibernacji.

  • w odniesieniu do prędkości: nowsze procesory Intel znoszą zmiany prędkości i zwracają dostosowaną liczbę. Zrobiłem szybkie skanowanie niektórych pudełek w naszej sieci i znalazłem tylko jedno pudełko, które nie miało tego : Pentium 3 z jakimś starym serwerem bazy danych. (są to pudełka linux, więc sprawdziłem z: grep constant_tsc/proc/cpuinfo)

  • nie jestem pewien co do AMD, jesteśmy przede wszystkim sklep Intel, chociaż wiem, że niektórzy z naszych niska -poziom guru dokonał oceny AMD na poziomie .

Nadzieja ta zaspokaja swoją ciekawość, jest to ciekawe i (IMHO) niedostatecznie badano obszar programowania. Wiesz, kiedy Jeff i Joel rozmawiali o tym, czy programista powinien znać C? Byłem krzycząc na nich, "hej zapomnieć, że rzeczy wysokiego poziomu C ... asembler jest to, co powinieneś się nauczyć, jeśli chcesz wiedzieć, co robi komputer !"

+1

... Ludzie jądra próbowali nakłonić ludzi do zaprzestania używania rdtsc przez jakiś czas ... i ogólnie unikają używania go w jądrze, ponieważ jest to po prostu niewiarygodne . – Spudd86

+1

Dla odniesienia, pytanie, które zadałem (w osobnej odpowiedzi - przed komentarzem) brzmiało: "Muszę przyznać, że większość twojego przykładu poszła prosto nad moją głową, ale kompiluje się i wydaje się działać. dla systemów SMP lub SpeedStep? " – Bernard

3

Czytając RDTSC nie jest niezawodny w systemach SMP, ponieważ każdy CPU utrzymuje swój własny licznik i każdy licznik nie jest gwarantowane przez zsynchronizowane w odniesieniu do innego procesora.

Mogę zasugerować wypróbowanie clock_gettime(CLOCK_REALTIME). Instrukcja posix wskazuje, że powinno to zostać wdrożone na wszystkich zgodnych systemach. Może zapewnić liczbę nanosekund, ale prawdopodobnie będziesz chciał sprawdzić w swoim systemie , aby zobaczyć, jaka jest rzeczywista rozdzielczość.

+0

'clock_getres (CLOCK_REALTIME)' nie daje rzeczywistej rozdzielczości. Zawsze zwraca "1 ns" (jedna nanosekunda), gdy dostępne są hrtimery, sprawdź plik 'include/linux/hrtimer.h' dla' define HIGH_RES_NSEC 1' (więcej na http://stackoverflow.com/a/23044075/196561) – osgx

5

This answer wymienia problemy z zegarem jest regulowana. Zarówno twoje problemy gwarantujące jednostki zaznaczania, jak i problemy z regulowanym czasem są rozwiązywane w C++ 11 przy pomocy biblioteki <chrono>.

Gwarantuje się, że zegar std::chrono::steady_clock nie będzie regulowany, a ponadto będzie się zwiększał ze stałą prędkością w stosunku do czasu rzeczywistego, więc technologie takie jak SpeedStep nie mogą na niego wpływać.

Jednostki uzyskujące typy odporne na uszkodzenia można przekształcić w jedną ze specjalizacji std::chrono::duration, na przykład std::chrono::microseconds. W tym typie nie ma dwuznaczności na temat jednostek używanych przez wartość tick. Należy jednak pamiętać, że zegar nie musi mieć tej rozdzielczości. Możesz zamienić czas trwania na attosekundy bez posiadania dokładnego zegara.