Come utilizzare QueryPerformanceCounter?


97

Recentemente ho deciso che dovevo passare dall'uso di millisecondi a microsecondi per la mia classe Timer e dopo alcune ricerche ho deciso che QueryPerformanceCounter è probabilmente la mia scommessa più sicura. (L'avvertimento Boost::Posixche potrebbe non funzionare sull'API Win32 mi ha scoraggiato un po '). Tuttavia, non sono davvero sicuro di come implementarlo.

Quello che sto facendo è chiamare qualsiasi GetTicks()funzione esque che sto usando e assegnarla alla startingTicksvariabile di Timer . Quindi, per trovare la quantità di tempo trascorso, sottraggo semplicemente il valore di ritorno della funzione da startingTicks, e quando resetto il timer chiamo di nuovo la funzione e le assegno i startingTicks. Sfortunatamente, dal codice che ho visto non è così semplice come chiamare QueryPerformanceCounter()e non sono sicuro di cosa dovrei passare come argomento.


2
Ho preso gli snippet di codice di Ramonster e li ho trasformati in una libreria qui: gist.github.com/1153062 per i follower.
rogerdpack

3
Abbiamo recentemente aggiornato la documentazione per QueryPerformanceCounter e aggiunto ulteriori informazioni sull'uso corretto e risposte alle domande frequenti. Puoi trovare la documentazione aggiornata qui msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs,

proprio come citare __rdtsc , è ciò che utilizza QueryPerformanceCounter.
colin lamarre

Risposte:


159
#include <windows.h>

double PCFreq = 0.0;
__int64 CounterStart = 0;

void StartCounter()
{
    LARGE_INTEGER li;
    if(!QueryPerformanceFrequency(&li))
    cout << "QueryPerformanceFrequency failed!\n";

    PCFreq = double(li.QuadPart)/1000.0;

    QueryPerformanceCounter(&li);
    CounterStart = li.QuadPart;
}
double GetCounter()
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return double(li.QuadPart-CounterStart)/PCFreq;
}

int main()
{
    StartCounter();
    Sleep(1000);
    cout << GetCounter() <<"\n";
    return 0;
}

Questo programma dovrebbe produrre un numero vicino a 1000 (il sonno di Windows non è così preciso, ma dovrebbe essere come 999).

La StartCounter()funzione registra il numero di tick che il contatore delle prestazioni ha nella CounterStartvariabile. La GetCounter()funzione restituisce il numero di millisecondi dall'ultima volta che è StartCounter()stata chiamata come double, quindi se GetCounter()restituisce 0,001, è passato circa 1 microsecondo da quando è StartCounter()stata chiamata.

Se vuoi che il timer usi i secondi, allora cambia

PCFreq = double(li.QuadPart)/1000.0;

per

PCFreq = double(li.QuadPart);

o se vuoi microsecondi, usa

PCFreq = double(li.QuadPart)/1000000.0;

Ma in realtà si tratta di comodità poiché restituisce un doppio.


5
Esattamente, cos'è LARGE_INTEGER?
Anonimo

5
è un tipo Windows, fondamentalmente un intero portatile a 64 bit. La sua definizione dipende dal fatto che il sistema di destinazione supporti o meno interi a 64 bit. Se il sistema non supporta gli int a 64 bit, viene definito come 2 int a 32 bit, un HighPart e un LowPart. Se il sistema supporta gli int a 64 bit, è un'unione tra i 2 int a 32 bit e un int a 64 bit chiamato QuadPart.
Ramónster

8
Questa risposta è gravemente imperfetta. QueryPerformanceCounter legge un registro del contatore del ciclo specifico del core e, se il thread di esecuzione è stato riprogrammato su un altro core, due misurazioni da QueryPerformanceCounter incorporano non solo il tempo trascorso, ma spesso un delta fisso, grande e difficile da individuare tra i due registri dei core. Quindi, questo funziona in modo affidabile solo come presentato se il tuo processo è legato a un core specifico.
Tony Delroy

15
@TonyD: La documentazione MSDN dice: On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL).Questo codice non è gravemente difettoso, ma alcuni BIOS o HAL.
Lucas

3
@ TonyD: ho appena esaminato questo aspetto un po 'di più. Ho aggiunto la seguente chiamata alla StartCounterfunzione: old_mask = SetThreadAffinityMask(GetCurrentThread,1);e poi l'ho reimpostata alla fine SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Spero che vada bene. Ciò dovrebbe impedire che il mio thread venga riprogrammato su qualsiasi cosa tranne il primo core della CPU. (Che ovviamente è solo una soluzione per un ambiente di test)
Lucas

19

Uso queste definizioni:

/** Use to init the clock */
#define TIMER_INIT \
    LARGE_INTEGER frequency; \
    LARGE_INTEGER t1,t2; \
    double elapsedTime; \
    QueryPerformanceFrequency(&frequency);


/** Use to start the performance timer */
#define TIMER_START QueryPerformanceCounter(&t1);

/** Use to stop the performance timer and output the result to the standard stream. Less verbose than \c TIMER_STOP_VERBOSE */
#define TIMER_STOP \
    QueryPerformanceCounter(&t2); \
    elapsedTime=(float)(t2.QuadPart-t1.QuadPart)/frequency.QuadPart; \
    std::wcout<<elapsedTime<<L" sec"<<endl;

Utilizzo (parentesi per evitare ridefinizioni):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Output dall'esempio di utilizzo:

1.00003 sec
1.23407 sec

2

Supponendo che tu sia su Windows (in tal caso dovresti contrassegnare la tua domanda come tale!), In questa pagina MSDN puoi trovare la fonte per una semplice e utile HRTimerclasse C ++ che racchiude le chiamate di sistema necessarie per fare qualcosa di molto vicino a ciò che ti serve (sarebbe facile aggiungervi un GetTicks()metodo, in particolare, per fare esattamente ciò di cui avete bisogno).

Su piattaforme non Windows, non è disponibile la funzione QueryPerformanceCounter, quindi la soluzione non sarà direttamente portabile. Tuttavia, se lo racchiudi in una classe come quella sopra menzionata HRTimer, sarà più facile cambiare l'implementazione della classe per utilizzare ciò che la piattaforma attuale è effettivamente in grado di offrire (forse tramite Boost o qualsiasi altra cosa!).


1

Estenderei questa domanda con un esempio di driver NDIS su come ottenere il tempo. Come si sa, KeQuerySystemTime (imitato sotto NdisGetCurrentSystemTime) ha una bassa risoluzione sopra i millisecondi, e ci sono alcuni processi come i pacchetti di rete o altri IRP che potrebbero richiedere un timestamp migliore;

L'esempio è altrettanto semplice:

LONG_INTEGER data, frequency;
LONGLONG diff;
data = KeQueryPerformanceCounter((LARGE_INTEGER *)&frequency)
diff = data.QuadPart / (Frequency.QuadPart/$divisor)

dove il divisore è 10 ^ 3 o 10 ^ 6 a seconda della risoluzione richiesta.

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.