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.

Anonimowy
źródło
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ą.

Ramónster
źródło
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
kratka wentylacyjna
źródło
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!).

Alex Martelli
źródło
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.

kagali-san
źródło