¿Cómo utilizar QueryPerformanceCounter?

97

Recientemente decidí que necesitaba cambiar de milisegundos a microsegundos para mi clase de temporizador, y después de algunas investigaciones, decidí que QueryPerformanceCounter es probablemente mi apuesta más segura. (La advertencia de Boost::Posixque puede que no funcione en la API de Win32 me desanimó un poco). Sin embargo, no estoy muy seguro de cómo implementarlo.

Lo que estoy haciendo es llamar a cualquier GetTicks()función esque que esté usando y asignarla a la startingTicksvariable de Timer . Luego, para encontrar la cantidad de tiempo transcurrido, simplemente resto el valor de retorno de la función del startingTicks, y cuando restablezco el temporizador, simplemente llamo a la función nuevamente y le asigno initialTicks. Desafortunadamente, por el código que he visto, no es tan simple como llamar QueryPerformanceCounter(), y no estoy seguro de qué se supone que debo pasar como argumento.

Anónimo
fuente
2
Tomé los fragmentos de código de Ramonster y los convertí en una biblioteca aquí: gist.github.com/1153062 para seguidores.
rogerdpack
3
Recientemente, actualizamos la documentación de QueryPerformanceCounter y agregamos información adicional sobre el uso adecuado y respuestas a las preguntas frecuentes. Puede encontrar la documentación actualizada aquí msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs
solo como mencionar __rdtsc , es lo que usa QueryPerformanceCounter.
colin lamarre

Respuestas:

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

Este programa debería generar un número cercano a 1000 (la suspensión de Windows no es tan precisa, pero debería ser como 999).

La StartCounter()función registra el número de ticks que tiene el contador de rendimiento en la CounterStartvariable. La GetCounter()función devuelve el número de milisegundos desde la StartCounter()última vez que se llamó como un doble, por lo que si GetCounter()devuelve 0,001, ha pasado aproximadamente 1 microsegundo desde que StartCounter()se llamó.

Si desea que el temporizador utilice segundos en su lugar, cambie

PCFreq = double(li.QuadPart)/1000.0;

a

PCFreq = double(li.QuadPart);

o si quieres microsegundos, usa

PCFreq = double(li.QuadPart)/1000000.0;

Pero realmente se trata de conveniencia ya que devuelve un doble.

Ramónster
fuente
5
Exactamente, ¿qué es LARGE_INTEGER?
Anónimo
5
es un tipo de Windows, básicamente un entero portátil de 64 bits. Su definición depende de si el sistema de destino admite enteros de 64 bits o no. Si el sistema no admite entradas de 64 bits, entonces se define como 2 entradas de 32 bits, una HighPart y una LowPart. Si el sistema admite entradas de 64 bits, entonces es una unión entre las 2 entradas de 32 bits y una entrada de 64 bits llamada QuadPart.
Ramónster
8
Esta respuesta tiene muchos defectos. QueryPerformanceCounter lee un registro de contador de ciclo específico de un núcleo, y si el hilo de ejecución se ha reprogramado en otro núcleo, dos mediciones de QueryPerformanceCounter incorporan no solo el tiempo transcurrido, sino a menudo un delta fijo, grande y difícil de identificar entre los dos registros de núcleos. Entonces, esto solo funciona de manera confiable como se presenta si su proceso está vinculado a un núcleo específico.
Tony Delroy
15
@TonyD: La documentación de 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).Este código no tiene muchas fallas, pero algo de BIOS o HAL.
Lucas
3
@TonyD: Acabo de investigar esto un poco más. Agregué la siguiente llamada a la StartCounterfunción: old_mask = SetThreadAffinityMask(GetCurrentThread,1);y luego la configuré al final SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Espero que eso funcione. Esto debería evitar que mi hilo se reprograme a cualquier cosa que no sea el primer núcleo de la CPU. (Que obviamente es solo una solución para un entorno de prueba)
Lucas
19

Yo uso estos define:

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

Uso (corchetes para evitar redefiniciones):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Resultado del ejemplo de uso:

1.00003 sec
1.23407 sec
respiradero
fuente
2

Suponiendo que está en Windows (si es así, ¡debería etiquetar su pregunta como tal!), En esta página de MSDN puede encontrar la fuente de una HRTimerclase C ++ simple y útil que incluye las llamadas al sistema necesarias para hacer algo muy parecido a lo que necesita. (sería fácil agregarle un GetTicks()método, en particular, para hacer exactamente lo que necesita).

En plataformas que no son de Windows, no hay función QueryPerformanceCounter, por lo que la solución no será directamente portátil. Sin embargo, si lo envuelve en una clase como la mencionada anteriormente HRTimer, será más fácil cambiar la implementación de la clase para usar lo que la plataforma actual realmente puede ofrecer (¡tal vez a través de Boost o lo que sea!).

Alex Martelli
fuente
1

Ampliaría esta pregunta con un ejemplo de controlador NDIS sobre cómo obtener tiempo. Como se sabe, KeQuerySystemTime (imitado en NdisGetCurrentSystemTime) tiene una resolución baja por encima de milisegundos, y hay algunos procesos como paquetes de red u otros IRP que pueden necesitar una mejor marca de tiempo;

El ejemplo es igual de simple:

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

donde el divisor es 10 ^ 3 o 10 ^ 6 según la resolución requerida.

kagali-san
fuente