Jak korzystać z QueryPerformanceCounter?


97

Niedawno zdecydowałem, że muszę zmienić użycie milisekund na mikrosekundy dla mojej klasy Timer, a po kilku badaniach zdecydowałem, że QueryPerformanceCounter jest prawdopodobnie moim najbezpieczniejszym zakładem. (Ostrzeżenie Boost::Posix, że może nie działać na Win32 API, trochę mnie zraziło). Jednak nie jestem pewien, jak to zaimplementować.

To, co robię, to wywołanie dowolnej GetTicks()funkcji esque, której używam i przypisanie jej do startingTickszmiennej Timera . Następnie, aby znaleźć ilość czasu, który upłynął, po prostu odejmuję wartość zwracaną przez funkcję od wartości startingTicks, a kiedy resetuję licznik czasu, po prostu wywołuję funkcję ponownie i przypisuję do niej startTicks. Niestety, z kodu, który widziałem, nie jest tak proste, jak samo wywołanie QueryPerformanceCounter()i nie jestem pewien, co mam podać jako jego argument.


2
Pobrałem fragmenty kodu Ramonstera i umieściłem je w bibliotece tutaj: gist.github.com/1153062 dla obserwujących.
— rogerdpack

3
Niedawno zaktualizowaliśmy dokumentację QueryPerformanceCounter i dodaliśmy dodatkowe informacje dotyczące prawidłowego użytkowania oraz odpowiedzi na często zadawane pytania. Zaktualizowaną dokumentację można znaleźć tutaj msdn.microsoft.com/en-us/library/windows/desktop/ ...
— Ed Briggs,

chciałbym wspomnieć o __rdtsc , właśnie tego używa QueryPerformanceCounter.
— colin lamarre,

Odpowiedzi:


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

Ten program powinien wypisać liczbę bliską 1000 (stan uśpienia systemu Windows nie jest tak dokładny, ale powinien wynosić 999).

StartCounter()Funkcja rejestruje liczbę kleszczy licznik wydajność ma w CounterStartzmiennej. GetCounter()Funkcja zwraca liczbę milisekund od StartCounter()ostatnio zwanych jako podwójne, więc jeśli GetCounter()powróci 0.001 następnie został on około 1 mikrosekundy ponieważ StartCounter()został powołany.

Jeśli chcesz mieć timer, użyj zamiast tego sekund, a następnie zmień

PCFreq = double(li.QuadPart)/1000.0;

do

PCFreq = double(li.QuadPart);

lub jeśli chcesz mikrosekund, użyj

PCFreq = double(li.QuadPart)/1000000.0;

Ale tak naprawdę chodzi o wygodę, ponieważ zwraca podwójną.


5
Dokładnie, co to jest LARGE_INTEGER?
— Anonimowy

5
jest to typ systemu Windows, w zasadzie przenośna 64-bitowa liczba całkowita. Jego definicja zależy od tego, czy system docelowy obsługuje 64-bitowe liczby całkowite, czy nie. Jeśli system nie obsługuje 64-bitowych liczb int, to jest zdefiniowany jako 2 32-bitowe wartości int, HighPart i LowPart. Jeśli system obsługuje 64-bitowe liczby int, to jest to suma między 2 32-bitowymi liczbami int i 64-bitowymi liczbami int o nazwie QuadPart.
— Ramónster

8
Ta odpowiedź jest mocno błędna. QueryPerformanceCounter odczytuje specyficzny dla rdzenia rejestr licznika cykli, a jeśli wątek wykonania został przełożony na inny rdzeń, dwa pomiary z QueryPerformanceCounter uwzględniają nie tylko upływający czas, ale często stałą, dużą i trudną do określenia deltę między dwoma rejestrami rdzeni. Tak więc - działa to niezawodnie tylko wtedy, gdy jest on powiązany z określonym rdzeniem.
— Tony Delroy

15
@TonyD: Dokumentacja MSDN mówi: 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).Ten kod nie jest bardzo wadliwy, ale trochę BIOS lub HAL.
— Lucas

3
@TonyD: Po prostu przyjrzałem się temu trochę więcej. Dodałem do funkcji następujące wywołanie StartCounter: old_mask = SetThreadAffinityMask(GetCurrentThread,1);a następnie ustawiłem z powrotem na końcu SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Mam nadzieję, że to wystarczy. Powinno to zapobiec zmianie harmonogramu mojego wątku na cokolwiek innego niż pierwszy rdzeń procesora. (Co jest oczywiście tylko rozwiązaniem dla środowiska testowego)
— Lucas

19

Używam tych definicji:

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

Użycie (nawiasy, aby zapobiec przedefiniowaniu):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Dane wyjściowe z przykładu użycia:

1.00003 sec
1.23407 sec

2

Zakładając, że korzystasz z systemu Windows (jeśli tak, powinieneś oznaczyć swoje pytanie jako takie!), Na tej stronie MSDN możesz znaleźć źródło prostej, użytecznej HRTimerklasy C ++, która opakowuje potrzebne wywołania systemowe, aby zrobić coś bardzo zbliżonego do tego, czego potrzebujesz (byłoby łatwo dodać GetTicks()do niego metodę, w szczególności zrobić dokładnie to, czego potrzebujesz).

Na platformach innych niż Windows nie ma funkcji QueryPerformanceCounter, więc rozwiązanie nie będzie bezpośrednio przenośne. Jeśli jednak umieścisz go w klasie takiej jak wspomniana powyżej HRTimer, łatwiej będzie zmienić implementację klasy, aby korzystać z tego, co aktualnie platforma jest w stanie zaoferować (może przez Boost lub cokolwiek!).


1

Rozszerzyłbym to pytanie o przykład sterownika NDIS dotyczący uzyskiwania czasu. Jak wiadomo, KeQuerySystemTime (naśladowany w NdisGetCurrentSystemTime) ma niską rozdzielczość powyżej milisekund, a niektóre procesy, takie jak pakiety sieciowe lub inne IRP, mogą wymagać lepszego znacznika czasu;

Przykład jest równie prosty:

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

gdzie dzielnik to 10 ^ 3 lub 10 ^ 6 w zależności od wymaganej rozdzielczości.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.