Як користуватися QueryPerformanceCounter?


97

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

Те, що я роблю, викликає будь-яку GetTicks()функцію esque, яку я використовую, і присвоюю їй startingTicksзмінну Таймера . Потім, щоб знайти кількість пройденого часу, я просто віднімаю повернене значення функції від startingTicks, і коли я скидаю таймер, я просто знову викликаю функцію і призначаю їй StartTicks. На жаль, з коду, який я бачив, це не так просто, як просто дзвінок QueryPerformanceCounter(), і я не впевнений, що я повинен передати як його аргумент.


2
Я взяв фрагменти коду Рамонстера і переніс їх у бібліотеку: gist.github.com/1153062 для підписників.
rogerdpack

3
Нещодавно ми оновили документацію для QueryPerformanceCounter та додали додаткову інформацію для належного використання та відповіді на FAQ. Ви можете знайти оновлену документацію тут msdn.microsoft.com/en-us/library/windows/desktop/…
Ед Бріггс

просто як згадати __rdtsc , саме це використовує QueryPerformanceCounter.
colin lamarre

Відповіді:


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?
Анонім

5
це тип Windows, в основному це портативне 64-бітове ціле число. Це визначення залежить від того, підтримує цільова система 64-бітні цілі числа чи ні. Якщо система не підтримує 64-бітові входи, вона визначається як 2 32-бітові входи, HighPart і LowPart. Якщо система підтримує 64 бітові ints, то це об'єднання між 2 32-бітовими входами та 64-бітовим int під назвою 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 залежно від необхідної роздільної здатності.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.