Questa nuova risposta utilizza la funzionalità di C ++ 11 <chrono>. Mentre ci sono altre risposte che mostrano come usare <chrono>, nessuna di esse mostra come usare <chrono>con la RDTSCstruttura menzionata in molte delle altre risposte qui. Quindi ho pensato di mostrare come usare RDTSCcon <chrono>. Inoltre dimostrerò come si può modellare il codice di test sull'orologio in modo da poter passare rapidamente tra RDTSCe le funzionalità dell'orologio integrate nel sistema (che saranno probabilmente basate su clock(), clock_gettime()e / oQueryPerformanceCounter .
Notare che l' RDTSCistruzione è specifica per x86. QueryPerformanceCounterè solo Windows. Ed clock_gettime()è solo POSIX. Di seguito presento due nuovi orologi:std::chrono::high_resolution_clock e std::chrono::system_clock, che, se puoi assumere C ++ 11, ora sono multipiattaforma.
Innanzitutto, ecco come creare un clock compatibile con C ++ 11 dall'istruzione di rdtscassemblaggio Intel . Lo chiamerò x::clock:
#include <chrono>
namespace x
{
struct clock
{
typedef unsigned long long rep;
typedef std::ratio<1, 2'800'000'000> period; // My machine is 2.8 GHz
typedef std::chrono::duration<rep, period> duration;
typedef std::chrono::time_point<clock> time_point;
static const bool is_steady = true;
static time_point now() noexcept
{
unsigned lo, hi;
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return time_point(duration(static_cast<rep>(hi) << 32 | lo));
}
};
} // x
Tutto ciò che fa questo orologio è contare i cicli della CPU e memorizzarli in un numero intero a 64 bit senza segno. Potrebbe essere necessario modificare la sintassi del linguaggio assembly per il compilatore. Oppure il tuo compilatore potrebbe offrire un intrinseco che puoi usare al suo posto (ad esempio now() {return __rdtsc();}).
Per costruire un orologio devi dargli la rappresentazione (tipo di archiviazione). È inoltre necessario fornire il periodo di clock, che deve essere una costante del tempo di compilazione, anche se la macchina può modificare la velocità di clock in diverse modalità di alimentazione. E da questi puoi facilmente definire la durata del tempo "nativo" del tuo orologio e il punto temporale in termini di questi fondamentali.
Se tutto ciò che vuoi fare è visualizzare il numero di tick dell'orologio, non importa quale numero dai per il periodo dell'orologio. Questa costante entra in gioco solo se si desidera convertire il numero di battiti dell'orologio in un'unità in tempo reale come i nanosecondi. E in quel caso, più preciso sei in grado di fornire la velocità di clock, più accurata sarà la conversione in nanosecondi, (millisecondi, qualunque cosa).
Di seguito è riportato un codice di esempio che mostra come utilizzare x::clock. In realtà ho modellato il codice sull'orologio perché vorrei mostrare come è possibile utilizzare molti orologi diversi con la stessa identica sintassi. Questo particolare test mostra qual è l'overhead di loop quando si esegue ciò che si desidera cronometrare in un loop:
#include <iostream>
template <class clock>
void
test_empty_loop()
{
// Define real time units
typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
// or:
// typedef std::chrono::nanoseconds nanoseconds;
// Define double-based unit of clock tick
typedef std::chrono::duration<double, typename clock::period> Cycle;
using std::chrono::duration_cast;
const int N = 100000000;
// Do it
auto t0 = clock::now();
for (int j = 0; j < N; ++j)
asm volatile("");
auto t1 = clock::now();
// Get the clock ticks per iteration
auto ticks_per_iter = Cycle(t1-t0)/N;
std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
// Convert to real time units
std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
<< "ps per iteration\n";
}
La prima cosa che fa questo codice è creare un'unità "in tempo reale" per visualizzare i risultati. Ho scelto picosecondi, ma puoi scegliere qualsiasi unità che preferisci, sia integrale che in virgola mobile. Ad esempio, c'è un'unità prefabbricata std::chrono::nanosecondsche avrei potuto usare.
Come altro esempio, voglio stampare il numero medio di cicli di clock per iterazione come virgola mobile, quindi creo un'altra durata, basata su double, che ha le stesse unità del tick dell'orologio (chiamato Cyclenel codice).
Il ciclo è programmato con le chiamate clock::now()su entrambi i lati. Se vuoi nominare il tipo restituito da questa funzione è:
typename clock::time_point t0 = clock::now();
(come chiaramente mostrato x::clocknell'esempio, ed è anche vero per gli orologi forniti dal sistema).
Per ottenere una durata in termini di tick di clock in virgola mobile, è sufficiente sottrarre i due punti temporali e per ottenere il valore per iterazione, dividere tale durata per il numero di iterazioni.
È possibile ottenere il conteggio in qualsiasi durata utilizzando la count()funzione membro. Ciò restituisce la rappresentazione interna. Infine uso std::chrono::duration_castper convertire la durata Cyclein durata picosecondse stamparla.
Usare questo codice è semplice:
int main()
{
std::cout << "\nUsing rdtsc:\n";
test_empty_loop<x::clock>();
std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
test_empty_loop<std::chrono::high_resolution_clock>();
std::cout << "\nUsing std::chrono::system_clock:\n";
test_empty_loop<std::chrono::system_clock>();
}
Sopra eseguo il test usando il nostro fatto in casa x::clocke confronto questi risultati con l'utilizzo di due degli orologi forniti dal sistema: std::chrono::high_resolution_clocke std::chrono::system_clock. Per me questo stampa:
Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration
Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration
Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration
Ciò mostra che ciascuno di questi orologi ha un periodo di tick diverso, poiché i tick per iterazione sono molto diversi per ogni orologio. Tuttavia, se convertito in un'unità di tempo nota (ad esempio picosecondi), ottengo approssimativamente lo stesso risultato per ogni orologio (il tuo chilometraggio può variare).
Nota come il mio codice è completamente privo di "costanti di conversione magica". In effetti, ci sono solo due numeri magici nell'intero esempio:
- La velocità di clock della mia macchina per definire
x::clock.
- Il numero di iterazioni su cui eseguire il test. Se la modifica di questo numero fa variare notevolmente i risultati, probabilmente dovresti aumentare il numero di iterazioni o svuotare il computer dai processi concorrenti durante il test.