È garantito che gettimeofday () abbia una risoluzione di microsecondi?


97

Sto portando un gioco, che era stato originariamente scritto per l'API Win32, su Linux (beh, portando il port OS X del port Win32 su Linux).

L'ho implementato QueryPerformanceCounterfornendo gli uSecondi dall'inizio del processo:

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;
}

Questo, insieme a QueryPerformanceFrequency()dare una frequenza costante di 1000000, funziona bene sulla mia macchina , dandomi una variabile a 64 bit che contiene uSecondssin dall'avvio del programma.

Quindi è portatile? Non voglio scoprire che funziona in modo diverso se il kernel è stato compilato in un certo modo o qualcosa del genere. Comunque mi va bene che non sia portabile su qualcosa di diverso da Linux.

Risposte:


57

Può essere. Ma hai problemi più grandi. gettimeofday()può causare tempi errati se nel sistema sono presenti processi che modificano il timer (ad esempio, ntpd). Su un Linux "normale", però, credo che la risoluzione gettimeofday()sia di 10us. Può saltare avanti e indietro e il tempo, di conseguenza, in base ai processi in esecuzione sul sistema. Questo rende effettivamente la risposta alla tua domanda no.

Dovresti controllare gli clock_gettime(CLOCK_MONOTONIC)intervalli di tempo. Soffre di molti meno problemi a causa di cose come i sistemi multi-core e le impostazioni dell'orologio esterno.

Inoltre, esamina la clock_getres()funzione.


1
clock_gettime è presente solo su Linux più recente. altri sistemi hanno solo gettimeofday ()
vitaly.v.ch

3
@ vitaly.v.ch è POSIX quindi non è solo Linux e "newist"? anche le distribuzioni 'Enterprise' come Red Hat Enterprise Linux sono basate su 2.6.18 che ha clock_gettime quindi no, non molto nuovo .. (la data della pagina di manuale in RHEL è 2004-March-12, quindi è in circolazione da un po ') a meno che tu non lo sia parlando di VECCHI kernel DAVVERO FREAKING COSA intendi?
Spudd86

clock_gettime è stato incluso in POSIX nel 2001. per quanto ne so attualmente clock_gettime () implementato in Linux 2.6 e qnx. ma linux 2.4 è attualmente utilizzato in molti sistemi di produzione.
vitaly.v.ch

È stato introdotto nel 2001, ma non obbligatorio fino a POSIX 2008.
R .. GitHub STOP HELPING ICE

2
Dalle FAQ di Linux per lock_gettime (vedi la risposta di David Schlosnagle) "CLOCK_MONOTONIC ... è regolato in frequenza da NTP tramite adjtimex (). In futuro (sto ancora cercando di ottenere la patch) ci sarà un CLOCK_MONOTONIC_RAW che non lo farà essere modificato e avrà una correlazione lineare con i contatori hardware. " Non credo che l'orologio _RAW sia mai entrato nel kernel (a meno che non sia stato rinominato _HR, ma la mia ricerca suggerisce che anche gli sforzi siano stati abbandonati).
Tony Delroy

41

Tempi di sovraccarico ad alta risoluzione e bassi per processori Intel

Se utilizzi hardware Intel, ecco come leggere il contatore delle istruzioni in tempo reale della CPU. Ti dirà il numero di cicli della CPU eseguiti dall'avvio del processore. Questo è probabilmente il contatore più fine che puoi ottenere per la misurazione delle prestazioni.

Notare che questo è il numero di cicli della CPU. Su Linux puoi ottenere la velocità della CPU da / proc / cpuinfo e dividerla per ottenere il numero di secondi. La conversione di questo in un doppio è abbastanza utile.

Quando lo eseguo sulla mia scatola, ottengo

11867927879484732
11867927879692217
it took this long to call printf: 207485

Ecco la guida per sviluppatori Intel che offre tantissimi dettagli.

#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
Si noti che il TSC potrebbe non essere sempre sincronizzato tra i core, potrebbe arrestarsi o cambiare la sua frequenza quando il processore entra in modalità di alimentazione inferiore (e non si ha modo di saperlo), e in generale non è sempre affidabile. Il kernel è in grado di rilevare quando è affidabile, rilevare altre alternative come HPET e ACPI PM timer e selezionare automaticamente la migliore. È una buona idea usare sempre il kernel per il tempismo a meno che tu non sia veramente sicuro che il TSC sia stabile e monotono.
CesarB

12
Il TSC sulle piattaforme Core e superiori Intel è sincronizzato su più CPU e aumenta a una frequenza costante indipendentemente dagli stati di gestione dell'alimentazione. Vedere il manuale per sviluppatori di software Intel, vol. 3 Sezione 18.10. Tuttavia, la velocità con cui il contatore aumenta non è la stessa della frequenza della CPU. Il TSC aumenta alla "frequenza massima risolta della piattaforma, che è uguale al prodotto della frequenza del bus scalabile e del rapporto massimo del bus risolto" Manuale per sviluppatori di software Intel, vol. 3 Sezione 18.18.5. Questi valori si ottengono dai registri specifici del modello (MSR) della CPU.
sstock

7
È possibile ottenere la frequenza del bus scalabile e il rapporto bus massimo risolto interrogando i registri specifici del modello (MSR) della CPU come segue: Frequenza bus scalabile == MSR_FSB_FREQ [2: 0] id 0xCD, Rapporto bus massimo risolto == MSR_PLATFORM_ID [12: 8] id 0x17. Consultare l'Appendice B.1 di Intel SDM Vol.3 per interpretare i valori di registro. Puoi utilizzare gli strumenti msr su Linux per interrogare i registri. kernel.org/pub/linux/utils/cpu/msr-tools
sstock

1
Il tuo codice non dovrebbe essere CPUIDriutilizzato dopo la prima RDTSCistruzione e prima di eseguire il codice sottoposto a benchmark? Altrimenti, cosa impedisce al codice di benchmark di essere eseguito prima / in parallelo con il primo RDTSCe di conseguenza sottorappresentato nel RDTSCdelta?
Tony Delroy

18

@Bernard:

Devo ammetterlo, la maggior parte del tuo esempio mi è passato per la testa. Si compila e sembra funzionare, però. È sicuro per i sistemi SMP o SpeedStep?

Questa è una buona domanda ... Penso che il codice sia ok. Da un punto di vista pratico, lo usiamo nella mia azienda ogni giorno e giriamo su una gamma piuttosto ampia di scatole, da 2 a 8 core. Ovviamente, YMMV, ecc., Ma sembra essere un metodo di temporizzazione affidabile e poco sovraccarico (perché non effettua un cambio di contesto nello spazio di sistema).

In generale come funziona è:

  • dichiarare il blocco di codice come assemblatore (e volatile, quindi l'ottimizzatore lo lascerà in pace).
  • eseguire l'istruzione CPUID. Oltre a ottenere alcune informazioni sulla CPU (con cui non facciamo nulla), sincronizza il buffer di esecuzione della CPU in modo che i tempi non siano influenzati dall'esecuzione fuori ordine.
  • esegue l'esecuzione rdtsc (read timestamp). Questo recupera il numero di cicli della macchina eseguiti da quando il processore è stato ripristinato. Questo è un valore a 64 bit, quindi con le attuali velocità della CPU si avvolge ogni 194 anni circa. È interessante notare che, nel riferimento originale del Pentium, notano che si avvolge ogni 5800 anni circa.
  • l'ultima coppia di righe memorizza i valori dai registri nelle variabili hi e lo e li inserisce nel valore di ritorno a 64 bit.

Note specifiche:

  • l'esecuzione fuori ordine può causare risultati errati, quindi eseguiamo l'istruzione "cpuid" che oltre a darti alcune informazioni sulla cpu sincronizza anche qualsiasi esecuzione di istruzione fuori ordine.

  • La maggior parte dei sistemi operativi sincronizza i contatori delle CPU all'avvio, quindi la risposta è buona entro un paio di nanosecondi.

  • Il commento di ibernazione è probabilmente vero, ma in pratica probabilmente non ti interessano i tempi oltre i limiti di ibernazione.

  • per quanto riguarda speedtep: le nuove CPU Intel compensano i cambiamenti di velocità e restituiscono un conteggio corretto. Ho fatto una rapida scansione su alcune delle scatole sulla nostra rete e ho trovato solo una scatola che non ce l'aveva: un Pentium 3 con un vecchio server di database. (queste sono scatole Linux, quindi ho controllato con: grep constant_tsc / proc / cpuinfo)

  • Non sono sicuro delle CPU AMD, siamo principalmente un negozio Intel, anche se so che alcuni dei nostri guru dei sistemi di basso livello hanno fatto una valutazione AMD.

Spero che questo soddisfi la tua curiosità, è un'area di programmazione interessante e (IMHO) poco studiata. Hai presente quando Jeff e Joel stavano parlando se un programmatore dovesse conoscere o meno il C? Stavo gridando loro: "Ehi dimentica quella roba di alto livello in C ... l'assemblatore è ciò che dovresti imparare se vuoi sapere cosa sta facendo il computer!"


1
... Le persone del kernel hanno cercato di convincere le persone a smettere di usare rdtsc per un po '... e generalmente evitano di usarlo nel kernel perché è proprio così inaffidabile.
Spudd86

1
Per riferimento, la domanda che ho posto (in una risposta separata - prima dei commenti) è stata: "Devo ammettere, la maggior parte del tuo esempio mi è finita in testa. Si compila e sembra funzionare, però. È sicuro per Sistemi SMP o SpeedStep? "
Bernard



9

Quindi dice esplicitamente microsecondi, ma dice che la risoluzione dell'orologio di sistema non è specificata. Suppongo che la risoluzione in questo contesto significhi come verrà incrementata la più piccola quantità possibile?

La struttura dei dati è definita come unità di misura di microsecondi, ma ciò non significa che l'orologio o il sistema operativo sia effettivamente in grado di misurarli con precisione.

Come altre persone hanno suggerito, gettimeofday()è negativo perché l'impostazione dell'ora può causare l'inclinazione dell'orologio e annullare i calcoli. clock_gettime(CLOCK_MONOTONIC)è quello che vuoi e clock_getres()ti dirà la precisione del tuo orologio.


Quindi cosa succede nel tuo codice quando gettimeofday () salta avanti o indietro con l'ora legale?
mpez0

3
clock_gettime è presente solo su Linux più recente. altri sistemi hanno solo gettimeofday ()
vitaly.v.ch

8

La risoluzione effettiva di gettimeofday () dipende dall'architettura hardware. I processori Intel e le macchine SPARC offrono timer ad alta risoluzione che misurano i microsecondi. Altre architetture hardware ricadono sul timer del sistema, che in genere è impostato su 100 Hz. In questi casi, la risoluzione temporale sarà meno accurata.

Ho ottenuto questa risposta da Misurazione del tempo e timer ad alta risoluzione, parte I


6

Questa risposta menziona problemi con l'orologio che viene regolato. Sia i tuoi problemi che garantiscono le unità tick che i problemi con il tempo che viene regolato sono risolti in C ++ 11 con il<chrono> libreria.

L'orologio std::chrono::steady_clockÈ garantito che non verrà regolato e inoltre avanzerà a una velocità costante rispetto al tempo reale, quindi tecnologie come SpeedStep non devono influenzarlo.

È possibile ottenere unità di scrittura sicure convertendo in una delle std::chrono::durationspecializzazioni, ad esempio std::chrono::microseconds. Con questo tipo non c'è ambiguità sulle unità utilizzate dal valore tick. Tuttavia, tieni presente che l'orologio non ha necessariamente questa risoluzione. Puoi convertire una durata in attosecondi senza avere effettivamente un orologio così preciso.


4

Dalla mia esperienza e da quello che ho letto su Internet, la risposta è "No", non è garantita. Dipende dalla velocità della CPU, dal sistema operativo, dal tipo di Linux, ecc.


3

La lettura dell'RDTSC non è affidabile nei sistemi SMP, poiché ogni CPU mantiene il proprio contatore e ogni contatore non è garantito dalla sincronizzazione rispetto a un'altra CPU.

Potrei suggerire di provare clock_gettime(CLOCK_REALTIME). Il manuale posix indica che questo dovrebbe essere implementato su tutti i sistemi conformi. Può fornire un conteggio in nanosecondi, ma probabilmente vorrai controllare clock_getres(CLOCK_REALTIME)il tuo sistema per vedere qual è la risoluzione effettiva.


clock_getres(CLOCK_REALTIME)non darà la vera risoluzione. Si ritorna sempre "1 ns" (un nanosecondo) quando hrtimers sono disponibili, controllo include/linux/hrtimer.hdi file per il define HIGH_RES_NSEC 1(più a stackoverflow.com/a/23044075/196561 )
osgx
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.