Como usar QueryPerformanceCounter?

97

Recentemente, decidi que precisava mudar de milissegundos para microssegundos para minha classe Timer e, depois de alguma pesquisa, decidi que QueryPerformanceCounter é provavelmente minha aposta mais segura. (O aviso de Boost::Posixque pode não funcionar na API do Win32 me desanima). No entanto, não tenho certeza de como implementá-lo.

O que estou fazendo é chamar qualquer GetTicks()função esque que estou usando e atribuí-la à startingTicksvariável do temporizador . Em seguida, para descobrir a quantidade de tempo que passou, apenas subtraio o valor de retorno da função de startingTicks, e quando eu zero o cronômetro, chamo a função novamente e atribuo a ela os marcadores de início. Infelizmente, pelo código que vi, não é tão simples quanto chamar QueryPerformanceCounter()e não tenho certeza do que devo passar como argumento.

Anônimo
fonte
2
Peguei os trechos de código de Ramonster e os transformei em uma biblioteca aqui: gist.github.com/1153062 para seguidores.
rogerdpack de
3
Recentemente, atualizamos a documentação do QueryPerformanceCounter e adicionamos informações adicionais sobre o uso adequado e respostas às perguntas frequentes. Você pode encontrar a documentação atualizada aqui msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs
assim como mencionar __rdtsc , é o que QueryPerformanceCounter usa.
colin lamarre

Respostas:

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 deve gerar um número próximo a 1000 (o Windows Sleep não é tão preciso, mas deve ser como 999).

A StartCounter()função registra o número de ticks que o contador de desempenho possui na CounterStartvariável. A GetCounter()função retorna o número de milissegundos desde que StartCounter()foi chamada pela última vez como um double, portanto, se GetCounter()retornar 0,001, já se passou cerca de 1 microssegundo desde que StartCounter()foi chamada.

Se você quiser que o cronômetro use segundos em vez disso, mude

PCFreq = double(li.QuadPart)/1000.0;

para

PCFreq = double(li.QuadPart);

ou se você quiser microssegundos, use

PCFreq = double(li.QuadPart)/1000000.0;

Mas realmente é uma questão de conveniência, pois retorna um duplo.

Ramónster
fonte
5
Exatamente, o que é LARGE_INTEGER?
Anônimo
5
é um tipo do Windows, basicamente um inteiro portátil de 64 bits. Sua definição depende se o sistema de destino suporta inteiros de 64 bits ou não. Se o sistema não suportar ints de 64 bits, ele é definido como 2 ints de 32 bits, um HighPart e um LowPart. Se o sistema suportar ints de 64 bits, então é uma união entre os 2 ints de 32 bits e um int de 64 bits chamado QuadPart.
Ramónster de
8
Esta resposta é muito falha. QueryPerformanceCounter lê um registro de contador de ciclo específico de núcleo e, se o encadeamento de execução foi reprogramado em outro núcleo, duas medições de QueryPerformanceCounter incorporam não apenas o tempo decorrido, mas muitas vezes um delta fixo, grande e difícil de identificar entre os dois registros de núcleos. Portanto - isso só funciona de forma confiável conforme apresentado se o seu processo estiver vinculado a um núcleo específico.
Tony Delroy
15
@TonyD: A documentação do MSDN diz: 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 não é muito falho, mas algum BIOS ou HAL.
Lucas
3
@TonyD: Acabei de examinar isso um pouco mais. Eu adicionei a seguinte chamada à StartCounterfunção: old_mask = SetThreadAffinityMask(GetCurrentThread,1);e a defini novamente no final SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Espero que isso resolva o problema. Isso deve evitar que meu thread seja reprogramado para qualquer coisa, exceto o primeiro núcleo da CPU. (O que obviamente é apenas uma solução para um ambiente de teste)
Lucas
19

Eu uso estas definições:

/** 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 (colchetes para evitar redefinições):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Resultado do exemplo de uso:

1.00003 sec
1.23407 sec
respirar
fonte
2

Supondo que você esteja no Windows (se estiver, você deve marcar sua pergunta como tal!), Nesta página do MSDN você pode encontrar a fonte de uma HRTimerclasse C ++ simples e útil que envolve as chamadas de sistema necessárias para fazer algo muito próximo do que você precisa (seria fácil adicionar um GetTicks()método a ele, em particular, para fazer exatamente o que você precisa).

Em plataformas não Windows, não há função QueryPerformanceCounter, portanto, a solução não será diretamente portátil. No entanto, se você envolvê-lo em uma classe como a mencionada acima HRTimer, será mais fácil mudar a implementação da classe para usar o que a plataforma atual é realmente capaz de oferecer (talvez via Boost ou qualquer outra coisa!).

Alex Martelli
fonte
1

Gostaria de estender esta questão com um exemplo de driver NDIS sobre como obter tempo. Como se sabe, KeQuerySystemTime (imitado em NdisGetCurrentSystemTime) tem uma resolução baixa acima de milissegundos, e existem alguns processos como pacotes de rede ou outros IRPs que podem precisar de um carimbo de data / hora melhor;

O exemplo é tão simples:

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

onde divisor é 10 ^ 3 ou 10 ^ 6 dependendo da resolução necessária.

kagali-san
fonte