Как использовать QueryPerformanceCounter?

97

Недавно я решил, что мне нужно перейти от использования миллисекунд к микросекундам для моего класса Timer, и после некоторых исследований я решил, что QueryPerformanceCounter, вероятно, моя самая безопасная ставка. (Предупреждение о Boost::Posixтом, что он может не работать в Win32 API, меня немного оттолкнуло). Однако я не совсем уверен, как это реализовать.

Я вызываю любую GetTicks()функцию esque, которую использую, и назначаю ее startingTicksпеременной Timer . Затем, чтобы узнать количество прошедшего времени, я просто вычитаю возвращаемое значение функции из startingTicks, а когда я сбрасываю таймер, я просто снова вызываю функцию и назначаю ей startTicks. К сожалению, из кода, который я видел, это не так просто, как просто вызов QueryPerformanceCounter(), и я не уверен, что я должен передать в качестве аргумента.

Анонимный
источник
2
Я взял фрагменты кода Ramonster и превратил их в библиотеку здесь: gist.github.com/1153062 для подписчиков.
rogerdpack
3
Недавно мы обновили документацию для QueryPerformanceCounter и добавили дополнительную информацию о правильном использовании и ответы на часто задаваемые вопросы. Вы можете найти обновленную документацию здесь msdn.microsoft.com/en-us/library/windows/desktop/…
Эд Бриггс,
просто хочу упомянуть __rdtsc , это то, что использует QueryPerformanceCounter.
Колин Ламарр 07

Ответы:

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

Эта программа должна вывести число, близкое к 1000 (спящий режим Windows не такой точный, но должен быть как 999).

StartCounter()Функция записывает число клещей счетчика производительности имеет в CounterStartпеременном. GetCounter()Функция возвращает количество миллисекунд , прошедших с StartCounter()последней называлась как двойной, так что, если GetCounter()возвращается 0.001 , то это было около 1 мкс , так StartCounter()называется.

Если вы хотите, чтобы таймер использовал секунды вместо этого, измените

PCFreq = double(li.QuadPart)/1000.0;

к

PCFreq = double(li.QuadPart);

или если вам нужны микросекунды, используйте

PCFreq = double(li.QuadPart)/1000000.0;

Но на самом деле все дело в удобстве, поскольку оно возвращает двойное значение.

Рамонстер
источник
5
Что такое LARGE_INTEGER?
Anonymous
5
это тип Windows, в основном переносимое 64-битное целое число. Его определение зависит от того, поддерживает ли целевая система 64-битные целые числа или нет. Если система не поддерживает 64-битные целые числа, то она определяется как 2 32-битных целых числа, HighPart и LowPart. Если система поддерживает 64-битные целые числа, то это объединение двух 32-битных целых чисел и 64-битного целого числа, называемого QuadPart.
Ramónster
8
Этот ответ сильно ошибочен. QueryPerformanceCounter считывает регистр счетчика циклов, зависящий от ядра, и если поток выполнения был перепланирован на другом ядре, два измерения из QueryPerformanceCounter включают не только прошедшее время, но часто фиксированную, большую и трудно поддающуюся точному определению дельту между двумя регистрами ядра. Итак - это работает надежно, только если ваш процесс привязан к определенному ядру.
Тони Делрой
15
@TonyD: Документация MSDN говорит: 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).этот код не сильно испорчен, но есть BIOS или HAL.
Лукас
3
@TonyD: Я только что изучил это немного подробнее. Я добавил в StartCounterфункцию следующий вызов : old_mask = SetThreadAffinityMask(GetCurrentThread,1);а затем вернул его в конец SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Я надеюсь, что это поможет. Это должно предотвратить перенос моего потока на что-либо, кроме 1-го ядра процессора. (Очевидно, что это только решение для тестовой среды)
Лукас,
19

Я использую эти определения:

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

Использование (скобки для предотвращения переопределения):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Вывод из примера использования:

1.00003 sec
1.23407 sec
вентиляция
источник
2

Предполагая, что вы работаете в Windows (если это так, вы должны пометить свой вопрос как таковой!), На этой странице MSDN вы можете найти источник простого и полезного HRTimerкласса C ++, который обертывает необходимые системные вызовы для выполнения чего-то очень близкого к тому, что вам нужно (можно было бы легко добавить GetTicks()к нему метод, в частности, чтобы делать именно то , что вам нужно).

На платформах, отличных от Windows, функция QueryPerformanceCounter отсутствует, поэтому решение не будет напрямую переносимым. Однако, если вы обернете его в такой класс, как вышеупомянутый HRTimer, будет легче изменить реализацию класса, чтобы использовать то, что действительно может предложить текущая платформа (возможно, через Boost или что-то еще!).

Алекс Мартелли
источник
1

Я бы расширил этот вопрос на пример драйвера NDIS, чтобы узнать время. Как известно, KeQuerySystemTime (имитируемый в NdisGetCurrentSystemTime) имеет низкое разрешение, превышающее миллисекунды, и есть некоторые процессы, такие как сетевые пакеты или другие IRP, которым может потребоваться лучшая временная метка;

Пример такой же простой:

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

где делитель 10 ^ 3 или 10 ^ 6 в зависимости от требуемого разрешения.

кагали-сан
источник