Wie verwende ich QueryPerformanceCounter?

97

Ich habe kürzlich entschieden, dass ich für meine Timer-Klasse von Millisekunden auf Mikrosekunden umsteigen muss, und nach einigen Recherchen habe ich entschieden, dass QueryPerformanceCounter wahrscheinlich meine sicherste Wahl ist. (Die Warnung, Boost::Posixdass es unter Win32-API möglicherweise nicht funktioniert, hat mich etwas abgeschreckt). Ich bin mir jedoch nicht sicher, wie ich es implementieren soll.

Ich rufe die von GetTicks()mir verwendete Esque-Funktion auf und weise sie der Timer- startingTicksVariablen zu. Um die verstrichene Zeit zu ermitteln, subtrahiere ich einfach den Rückgabewert der Funktion von startingTicksund wenn ich den Timer zurücksetze, rufe ich die Funktion erneut auf und weise ihr Start-Ticks zu. Leider ist der Code, den ich gesehen habe, nicht so einfach wie das bloße Aufrufen QueryPerformanceCounter(), und ich bin mir nicht sicher, was ich als Argument übergeben soll.

Anonym
quelle
2
Ich habe Ramonsters Codefragmente genommen und sie hier in eine Bibliothek aufgenommen: gist.github.com/1153062 für Follower.
Rogerdpack
3
Wir haben kürzlich die Dokumentation für QueryPerformanceCounter aktualisiert und zusätzliche Informationen zur ordnungsgemäßen Verwendung sowie Antworten auf häufig gestellte Fragen hinzugefügt. Die aktualisierte Dokumentation finden Sie hier msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs
Genau wie __rdtsc erwähnt , wird QueryPerformanceCounter verwendet.
Colin Lamarre

Antworten:

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

Dieses Programm sollte eine Zahl nahe 1000 ausgeben (Windows Sleep ist nicht so genau, sollte aber 999 sein).

Die StartCounter()Funktion zeichnet die Anzahl der Ticks auf, die der Leistungsindikator in der CounterStartVariablen hat. Die GetCounter()Funktion gibt die Anzahl der Millisekunden seit dem StartCounter()letzten Aufruf als Double zurück. Wenn also GetCounter()0,001 zurückgegeben wird, ist seit dem StartCounter()Aufruf etwa 1 Mikrosekunde vergangen .

Wenn Sie möchten, dass der Timer stattdessen Sekunden verwendet, ändern Sie ihn

PCFreq = double(li.QuadPart)/1000.0;

zu

PCFreq = double(li.QuadPart);

oder wenn Sie Mikrosekunden wollen, dann verwenden Sie

PCFreq = double(li.QuadPart)/1000000.0;

Aber es geht wirklich um Bequemlichkeit, da es ein Doppel zurückgibt.

Ramónster
quelle
5
Was genau ist LARGE_INTEGER?
Anonym
5
Es ist ein Windows-Typ, im Grunde eine tragbare 64-Bit-Ganzzahl. Die Definition hängt davon ab, ob das Zielsystem 64-Bit-Ganzzahlen unterstützt oder nicht. Wenn das System keine 64-Bit-Ints unterstützt, werden 2 32-Bit-Ints, ein HighPart und ein LowPart definiert. Wenn das System 64-Bit-Ints unterstützt, handelt es sich um eine Verbindung zwischen den 2 32-Bit-Ints und einem 64-Bit-Int namens QuadPart.
Ramónster
8
Diese Antwort ist stark fehlerhaft. QueryPerformanceCounter liest ein kernspezifisches Zykluszählerregister. Wenn der Ausführungsthread auf einem anderen Kern neu geplant wurde, enthalten zwei Messungen von QueryPerformanceCounter nicht nur die verstrichene Zeit, sondern häufig ein festes, großes und schwer zu lokalisierendes Delta zwischen den beiden Kernregistern. Dies funktioniert also nur dann zuverlässig, wenn Ihr Prozess an einen bestimmten Kern gebunden ist.
Tony Delroy
15
@TonyD: In der MSDN-Dokumentation heißt es: 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).Dieser Code ist nicht stark fehlerhaft, sondern ein BIOS oder HAL.
Lucas
3
@ TonyD: Ich habe mir das nur ein bisschen genauer angesehen. Ich habe der StartCounterFunktion den folgenden Aufruf hinzugefügt : old_mask = SetThreadAffinityMask(GetCurrentThread,1);und ihn am Ende zurückgesetzt SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Ich hoffe, das wird den Trick machen. Dies sollte verhindern, dass mein Thread auf etwas anderes als den 1. CPU-Kern verschoben wird. (Was offensichtlich nur eine Lösung für eine Testumgebung ist)
Lucas
19

Ich benutze diese Definitionen:

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

Verwendung (Klammern zur Vermeidung von Neudefinitionen):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Ausgabe aus Verwendungsbeispiel:

1.00003 sec
1.23407 sec
entlüften
quelle
2

Angenommen, Sie arbeiten unter Windows (wenn ja, sollten Sie Ihre Frage als solche kennzeichnen !), Finden Sie auf dieser MSDN-Seite die Quelle für eine einfache, nützliche HRTimerC ++ - Klasse, die die erforderlichen Systemaufrufe umschließt, um etwas zu tun, das Ihren Anforderungen sehr nahe kommt (Es wäre einfach, eine GetTicks()Methode hinzuzufügen , insbesondere um genau das zu tun , was Sie benötigen).

Auf Nicht-Windows-Plattformen gibt es keine QueryPerformanceCounter-Funktion, sodass die Lösung nicht direkt portierbar ist. Wenn Sie es jedoch in eine Klasse wie die oben genannte einbinden HRTimer, ist es einfacher, die Implementierung der Klasse zu ändern, um das zu verwenden, was die aktuelle Plattform tatsächlich bieten kann (möglicherweise über Boost oder was auch immer!).

Alex Martelli
quelle
1

Ich würde diese Frage mit einem NDIS-Treiberbeispiel zum Erhalten von Zeit erweitern. Wie bekannt ist, hat KeQuerySystemTime (nachgeahmt unter NdisGetCurrentSystemTime) eine niedrige Auflösung über Millisekunden, und es gibt einige Prozesse wie Netzwerkpakete oder andere IRPs, die möglicherweise einen besseren Zeitstempel benötigen.

Das Beispiel ist genauso einfach:

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

wobei der Divisor 10 ^ 3 oder 10 ^ 6 ist, abhängig von der erforderlichen Auflösung.

Kagali-San
quelle