Ci sono molte informazioni confuse e/o sbagliate sul TSC là fuori, quindi ho pensato di provare a chiarirne alcune.
Quando Intel ha introdotto per la prima volta il TSC (nelle CPU Pentium originali) è stato chiaramente documentato per contare i cicli (e non il tempo). Tuttavia, allora le CPU funzionavano principalmente a una frequenza fissa, quindi alcune persone ignoravano il comportamento documentato e lo usavano invece per misurare il tempo (in particolare, gli sviluppatori del kernel Linux). Il loro codice si è rotto nelle CPU successive che non funzionano a una frequenza fissa (a causa della gestione dell'alimentazione, ecc.). In quel periodo altri produttori di CPU (AMD, Cyrix, Transmeta, ecc.) erano confusi e alcuni implementarono TSC per misurare i cicli e alcuni lo implementarono in modo che misurasse il tempo, e alcuni lo resero configurabile (tramite un MSR).
Quindi i sistemi "multi-chip" sono diventati più comuni per i server; e anche più tardi fu introdotto il multi-core. Ciò ha portato a piccole differenze tra i valori TSC su core diversi (a causa dei diversi tempi di avvio); ma, cosa ancora più importante, ha anche portato a grandi differenze tra i valori TSC su CPU diverse causate da CPU che funzionano a velocità diverse (a causa della gestione dell'alimentazione e/o di altri fattori).
Le persone che cercavano di usarlo in modo sbagliato fin dall'inizio (persone che lo usavano per misurare il tempo e non i cicli) si sono lamentate molto e alla fine hanno convinto i produttori di CPU a standardizzare il fatto che il TSC misurasse il tempo e non i cicli.
Ovviamente questo era un disastro - ad es. ci vuole molto codice solo per determinare cosa misura effettivamente il TSC se supporti tutte le CPU 80x86; e diverse tecnologie di gestione dell'alimentazione (incluse cose come SpeedStep, ma anche cose come gli stati di sospensione) possono influenzare TSC in modi diversi su CPU diverse; quindi AMD ha introdotto un flag "TSC invariant" nel CPUID per indicare al sistema operativo che il TSC può essere utilizzato per misurare correttamente il tempo.
Tutte le recenti CPU Intel e AMD sono così da un po' di tempo:TSC conta il tempo e non misura affatto i cicli. Ciò significa che se si desidera misurare i cicli è necessario utilizzare i contatori di monitoraggio delle prestazioni (specifici del modello). Sfortunatamente i contatori di monitoraggio delle prestazioni sono un pasticcio ancora peggiore (a causa della natura specifica del modello e della configurazione contorta).
Finché il tuo thread rimane sullo stesso core della CPU, l'istruzione RDTSC continuerà a restituire un numero crescente fino a quando non si avvolge. Per una CPU da 2 GHz, questo accade dopo 292 anni, quindi non è un vero problema. Probabilmente non lo vedrai accadere. Se prevedi di vivere così a lungo, assicurati che il tuo computer si riavvii, diciamo, ogni 50 anni.
Il problema con RDTSC è che non hai alcuna garanzia che si avvii nello stesso momento su tutti i core di una vecchia CPU multicore e nessuna garanzia che si avvii nello stesso momento su tutte le CPU su una vecchia scheda multi-CPU .
I sistemi moderni di solito non hanno tali problemi, ma il problema può essere aggirato anche su sistemi più vecchi impostando l'affinità di un thread in modo che funzioni solo su una CPU. Questo non va bene per le prestazioni dell'applicazione, quindi generalmente non si dovrebbe farlo, ma per misurare i tick va bene.
(Un altro "problema" è che molte persone usano RDTSC per misurare il tempo, il che non cosa fa, ma hai scritto che vuoi i cicli della CPU, quindi va bene. Se lo fai usa RDTSC per misurare il tempo, potresti avere sorprese quando il risparmio energetico o l'hyperboost o come si chiama la moltitudine di tecniche di cambio di frequenza entrano in gioco. Per il tempo reale, il clock_gettime
syscall è sorprendentemente buono sotto Linux.)
Scriverei semplicemente rdtsc
all'interno del asm
statement, che funziona perfettamente per me ed è più leggibile di un oscuro codice esadecimale. Supponendo che sia il codice esadecimale corretto (e dal momento che non si arresta in modo anomalo e non restituisce un numero sempre crescente, sembra di sì), il tuo codice è buono.
Se vuoi misurare il numero di tick richiesti da un pezzo di codice, vuoi una differenza di tick , devi solo sottrarre due valori del contatore sempre crescente. Qualcosa come uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0;
Si noti che se sono necessarie misurazioni molto accurate isolate dal codice circostante, è necessario serializzare, ovvero bloccare la pipeline, prima di chiamare rdtsc
(o usa rdtscp
che è supportato solo sui processori più recenti). L'unica istruzione di serializzazione che può essere utilizzata a ogni livello di privilegio è cpuid
.
In risposta all'ulteriore domanda nel commento:
Il TSC parte da zero quando accendi il computer (e il BIOS reimposta tutti i contatori su tutte le CPU allo stesso valore, anche se alcuni BIOS alcuni anni fa non lo facevano in modo affidabile).
Pertanto, dal punto di vista del tuo programma, il contatore è iniziato "in un momento sconosciuto nel passato" e aumenta sempre con ogni tick dell'orologio che la CPU vede. Pertanto, se esegui l'istruzione che restituisce quel contatore ora e in qualsiasi momento successivo in un processo diverso, restituirà un valore maggiore (a meno che la CPU non sia stata sospesa o spenta nel mezzo). Diverse esecuzioni dello stesso programma ottengono numeri più grandi, perché il contatore continua a crescere. Sempre.
Ora, clock_gettime(CLOCK_PROCESS_CPUTIME_ID)
è una questione diversa. Questo è il tempo della CPU che il sistema operativo ha concesso al processo. Inizia da zero quando inizia il tuo processo. Anche un nuovo processo inizia da zero. Pertanto, due processi in esecuzione uno dopo l'altro otterranno numeri molto simili o identici, non sempre crescenti.
clock_gettime(CLOCK_MONOTONIC_RAW)
è più vicino a come funziona RDTSC (e su alcuni sistemi più vecchi è implementato con esso). Restituisce un valore che aumenta sempre. Al giorno d'oggi, questo è tipicamente un HPET. Tuttavia, questo è davvero tempo , e non tick . Se il tuo computer entra in uno stato di basso consumo (ad es. funziona a metà della frequenza normale), si fermerà avanzare allo stesso ritmo.
già buone risposte, e Damon lo ha già menzionato in un certo senso nella sua risposta, ma lo aggiungerò dalla voce del manuale x86 effettivo (volume 2, 4-301) per RDTSC:
Carica il valore corrente del contatore timestamp del processore (un MSR a 64 bit) nei registri EDX:EAX. Il registro EDX viene caricato con i 32 bit di ordine superiore dell'MSR e il registro EAX viene caricato con i 32 bit di ordine inferiore. (Sui processori che supportano l'architettura Intel 64, i 32 bit di ordine superiore di ciascuno di RAX e RDX vengono cancellati.)
Il processore incrementa in modo monotono il contatore di timestamp MSR a ogni ciclo di clock e lo reimposta su 0 ogni volta che il processore viene reimpostato. Vedere "Time Stamp Counter" nel capitolo 17 del Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 3B , per dettagli specifici sul comportamento del contatore timestamp.