Comment utiliser QueryPerformanceCounter?

97

J'ai récemment décidé que je devais passer de l'utilisation des millisecondes aux microsecondes pour ma classe Timer, et après quelques recherches, j'ai décidé que QueryPerformanceCounter était probablement mon pari le plus sûr. (L'avertissement sur le fait Boost::Posixque cela peut ne pas fonctionner sur l'API Win32 m'a un peu découragé). Cependant, je ne sais pas vraiment comment le mettre en œuvre.

Ce que je fais, c'est appeler la GetTicks()fonction esque que j'utilise et l'attribuer à la startingTicksvariable de Timer . Ensuite, pour trouver le temps écoulé, je soustrais simplement la valeur de retour de la fonction de startingTicks, et lorsque je réinitialise le minuteur, je rappelle simplement la fonction et lui attribue startTicks. Malheureusement, d'après le code que j'ai vu, ce n'est pas aussi simple que d'appeler QueryPerformanceCounter(), et je ne suis pas sûr de ce que je suis censé passer comme argument.

Anonyme
la source
2
J'ai pris les extraits de code de Ramonster et les ai transformés en bibliothèque ici: gist.github.com/1153062 pour les abonnés.
rogerdpack
3
Nous avons récemment mis à jour la documentation de QueryPerformanceCounter, et ajouté des informations supplémentaires sur l'utilisation correcte et des réponses à la FAQ. Vous pouvez trouver la documentation mise à jour ici msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs
comme pour mentionner __rdtsc , c'est ce que QueryPerformanceCounter utilise.
colin lamarre

Réponses:

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

Ce programme devrait afficher un nombre proche de 1000 (Windows Sleep n'est pas aussi précis, mais il devrait être comme 999).

La StartCounter()fonction enregistre le nombre de graduations du compteur de performance dans la CounterStartvariable. La GetCounter()fonction renvoie le nombre de millisecondes depuis StartCounter()son dernier appel sous forme de double, donc si GetCounter()renvoie 0,001, cela fait environ 1 microseconde depuisStartCounter() son appel.

Si vous voulez que la minuterie utilise les secondes à la place, changez

PCFreq = double(li.QuadPart)/1000.0;

à

PCFreq = double(li.QuadPart);

ou si vous voulez des microsecondes, utilisez

PCFreq = double(li.QuadPart)/1000000.0;

Mais c'est vraiment une question de commodité puisqu'elle renvoie un double.

Ramónster
la source
5
Exactement, qu'est-ce que LARGE_INTEGER?
Anonyme
5
c'est un type Windows, essentiellement un entier 64 bits portable. Sa définition dépend du fait que le système cible prend en charge les entiers 64 bits ou non. Si le système ne prend pas en charge les entiers 64 bits, il est défini comme 2 entiers 32 bits, un HighPart et un LowPart. Si le système prend en charge les entiers 64 bits, il s'agit d'une union entre les 2 entiers 32 bits et un int 64 bits appelé QuadPart.
Ramónster
8
Cette réponse est très imparfaite. QueryPerformanceCounter lit un registre de compteur de cycle spécifique au cœur, et si le thread d'exécution a été replanifié sur un autre cœur, deux mesures de QueryPerformanceCounter incorporent non seulement le temps écoulé, mais souvent un delta fixe, grand et difficile à localiser entre les deux registres de cœurs. Donc - cela ne fonctionne de manière fiable que présenté si votre processus est lié à un noyau spécifique.
Tony Delroy
15
@TonyD: La documentation MSDN dit: 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).Ce code n'est pas très imparfait, mais certains BIOS ou HAL.
Lucas
3
@TonyD: J'ai juste examiné cela un peu plus. J'ai ajouté l'appel suivant dans la StartCounterfonction: old_mask = SetThreadAffinityMask(GetCurrentThread,1);puis le remettre à la fin SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. J'espère que cela fera l'affaire. Cela devrait empêcher mon thread d'être replanifié à autre chose que le premier cœur de processeur. (Ce qui n'est évidemment qu'une solution pour un environnement de test)
Lucas
19

J'utilise ces définitions:

/** 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;

Utilisation (parenthèses pour éviter les redéfinitions):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Sortie de l'exemple d'utilisation:

1.00003 sec
1.23407 sec
évent
la source
2

En supposant que vous êtes sous Windows (si c'est le cas, vous devez marquer votre question comme telle!), Sur cette page MSDN vous pouvez trouver la source d'une HRTimerclasse C ++ simple et utile qui encapsule les appels système nécessaires pour faire quelque chose de très proche de ce dont vous avez besoin (il serait facile d'y ajouter une GetTicks()méthode, en particulier, pour faire exactement ce dont vous avez besoin).

Sur les plates-formes non Windows, il n'y a pas de fonction QueryPerformanceCounter, donc la solution ne sera pas directement portable. Cependant, si vous l'enveloppez dans une classe telle que celle mentionnée ci-dessus HRTimer, il sera plus facile de changer l'implémentation de la classe pour utiliser ce que la plate-forme actuelle est effectivement en mesure d'offrir (peut-être via Boost ou autre!).

Alex Martelli
la source
1

Je voudrais étendre cette question avec un exemple de pilote NDIS sur l'obtention du temps. Comme on le sait, KeQuerySystemTime (imité sous NdisGetCurrentSystemTime) a une faible résolution au-dessus des millisecondes, et il existe certains processus comme les paquets réseau ou d'autres IRP qui peuvent avoir besoin d'un meilleur horodatage;

L'exemple est tout aussi simple:

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

où le diviseur est 10 ^ 3 ou 10 ^ 6 selon la résolution requise.

kagali-san
la source