QueryPerformanceCounter und überläufe
Ich bin mit QueryPerformanceCounter, um einige timing in meiner Anwendung. Jedoch, nach dem ausführen es für ein paar Tage die Anwendung scheint nicht mehr korrekt funktioniert. Wenn ich einfach die Anwendung neu starten, es beginnt wieder zu arbeiten. Dies macht mich glauben, ich habe eine overflow-problem in meinem timing-code.
//Author: Ryan M. Geiss
//http://www.geisswerks.com/ryan/FAQS/timing.html
class timer
{
public:
timer()
{
QueryPerformanceFrequency(&freq_);
QueryPerformanceCounter(&time_);
}
void tick(double interval)
{
LARGE_INTEGER t;
QueryPerformanceCounter(&t);
if (time_.QuadPart != 0)
{
int ticks_to_wait = static_cast<int>(static_cast<double>(freq_.QuadPart) * interval);
int done = 0;
do
{
QueryPerformanceCounter(&t);
int ticks_passed = static_cast<int>(static_cast<__int64>(t.QuadPart) - static_cast<__int64>(time_.QuadPart));
int ticks_left = ticks_to_wait - ticks_passed;
if (t.QuadPart < time_.QuadPart) //time wrap
done = 1;
if (ticks_passed >= ticks_to_wait)
done = 1;
if (!done)
{
//if > 0.002s left, do Sleep(1), which will actually sleep some
// steady amount, probably 1-2 ms,
// and do so in a nice way (cpu meter drops; laptop battery spared).
//otherwise, do a few Sleep(0)'s, which just give up the timeslice,
// but don't really save cpu or battery, but do pass a tiny
// amount of time.
if (ticks_left > static_cast<int>((freq_.QuadPart*2)/1000))
Sleep(1);
else
for (int i = 0; i < 10; ++i)
Sleep(0); //causes thread to give up its timeslice
}
}
while (!done);
}
time_ = t;
}
private:
LARGE_INTEGER freq_;
LARGE_INTEGER time_;
};
Meine Frage ist, ob der obige code sollte funktionieren deterministisch für Wochen im Dauerbetrieb laufen?
Ist und wenn nicht wo das problem ist? Ich dachte, der überlauf wurde bearbeitet von
if (t.QuadPart < time_.QuadPart) //time wrap
done = 1;
Aber vielleicht ist das nicht genug?
EDIT: Bitte beachten, dass ich nicht schreiben der original-code, Ryan M. Geiss, habe den link auf die ursprüngliche Quelle der code ist in der code.
static_cast<int>((freq_.QuadPart*2)/1000))
- dies wird schneiden Ihnen alles, was darüber hinaus 32-bit. Sie würde eher erweiternticks_left
zu 64 bits.- Denkst du das ist ein problem, obwohl? Sollte nicht QueryPerformanceFrequency immer passen in einen 32-bit integer?
- Keine Ahnung, ich finde einfach diesen code möglicherweise falsch.
- Wann, glauben Sie, ticks_left wäre größer als 32 bit?
- Ja, das sollte wohl geändert werden. Ich versuche nur, um herauszufinden, mit einiger Sicherheit, ob es das problem verursachen könnte oder nicht. So dauert es ~1 Woche zu testen, um es zu überprüfen.
- Warum würden Sie wollen, führen Sie eine (teilweise) busy-wait wie diese?
- Zu Verbesserung der Genauigkeit der warten.
- Warum aber warten überhaupt?
- Da eine der Anforderungen meiner Anwendung ist, dass es braucht, um "tick" (ausführen einer bestimmten Funktion) in genauen Abständen. Dies ist aufgrund der Abhängigkeit von einer Drittanbieter-Komponente.
- Das problem mit
ticks_left
32 bit ist nicht so, dass die Anzahl der Zecken können zu groß, um fit in den 32 bit, das problem ist die Rechte Seite des Vergleichs zwingt eine 64-bit-Wert in 32 bits, und dies kann die Rendite einen negativen Wert ein, und klicken Sie Vergleich bewertet werden, falsch.
Du musst angemeldet sein, um einen Kommentar abzugeben.
QueryPerformanceCounter
ist berüchtigt für seine Unzuverlässigkeit. Es ist gut zu verwenden für individuelle kurz-Intervall timing, wenn Sie bereit sind, zu behandeln abnorme Ergebnisse. Es ist nicht genau - Es ist in der Regel basierend auf dem PCI-bus-Frequenz, und einem stark belasteten bus kann dazu führen, verloren Zecken.GetTickCount
ist tatsächlich stabiler, und kann Ihnen 1ms Auflösung, wenn Sie angerufen habentimeBeginPeriod
. Es wird schließlich wickeln, so müssen Sie damit umgehen.__rdtsc
sollte nicht verwendet werden, es sei denn, Sie ' re Profilerstellung und Kontrolle über die Kern laufen Sie auf und sind bereit, um variable CPU-Frequenz.GetSystemTime
ist in Ordnung, für längere Messungen, aber springen wird, wenn das system Zeit angepasst.Auch
Sleep(0)
nicht tun, was Sie denken, es tut. Es wird der Ertrag der cpu wenn ein anderer Kontext will es - ansonsten werde es sofort wieder zurück.Kurzum, das timing Fenster ist ein Chaos. Man würde denken, dass heute, es würde möglich sein, eine genaue Langzeit-timing von einem computer ohne Umweg über Reifen - aber das ist nicht der Fall. In unserem Spiel-framework verwenden wir einige Zeit Quellen und Korrekturen vom server, um sicherzustellen, dass alle angeschlossenen clients haben die gleiche Spielzeit, und es gibt eine Menge von schlechten Uhren, die es gibt.
Ihre beste Wette wäre wahrscheinlich einfach GetTickCount oder GetSystemTime, wickeln Sie es in etwas, das passt in die Zeit springt/wrap-around.
Außerdem sollten Sie konvertieren Sie Ihre
double interval
zu einemint64 milliseconds
und dann verwenden Sie ausschließlich integer-math - dies vermeidet Probleme aufgrund von floating-point-Typen " unterschiedliche Genauigkeit basierend auf deren Inhalt.timeBeginPeriod()
scheinen nicht zu bewirken, die Auflösung derGetTickCount()
für mich. Ich denke, du meintesttimeGetTime()
stattGetTickCount()
.timeBeginPeriod()
tun/habe Einfluss aufGetTickCount()
Auflösung auf XP, und ich glaube Vista. Ich hatte bisher keinen Bedarf für noch getestet, seitdemLeistungsindikatoren 64-bit, damit sind Sie groß genug für die Jahre kontinuierlich ausgeführt. Zum Beispiel, wenn Sie davon ausgehen, die performance-counter-Schritten 2 Milliarden mal in jeder Sekunde (eingebildeten 2-GHz-Prozessor) überlauf wird in etwa 290 Jahren.
Basierend auf Ihren Kommentar, sollten Sie wahrscheinlich verwenden werden Wartemöglichkeit Timer statt.
Siehe die folgenden Beispiele:
Sleep(1)
die schlafen können, ein paar ms.Mithilfe einer Nanosekunden-Skala timer zu Steuern, sowas wie Sleep (), die am besten ist präzise auf mehrere Millisekunden (und in der Regel mehrere Dutzend Millisekunden) etwas controversary sowieso.
Einen anderen Ansatz könnte man erwägen wäre, die Nutzung WaitForSingleObject oder eine ähnliche Funktion. Dieser verbrennt weniger CPU-Zyklen, verursacht eine Billion weniger context-switches über den Tag, und ist zuverlässiger als Sleep(0), zu.
Können Sie zum Beispiel erstellen Sie eine semapore und berühren Sie niemals im normalen Betrieb. Der semaphor existiert nur so können Sie warten auf etwas, wenn Sie nicht haben nichts besseres zu warten. Dann können Sie ein timeout in Millisekunden, bis zu 49 Tage lang mit einem einzigen Systemaufruf. Und, es wird nicht nur weniger Arbeit, es wird viel genauer zu.
Der Vorteil ist, dass wenn "etwas passiert", so dass Sie wollen, brechen früher auf als Sie müssen nur signal semaphore. Der wait-Aufruf zurück sofort, und Sie wissen, von den Rückgabewert WAIT_OBJECT_0, dass es wegen wird signalisiert, nicht durch die Zeit läuft aus. Und das alles ohne komplizierte Logik und zählen von Zyklen.
Das problem, das Sie gefragt über die meisten direkt:
if (t.QuadPart < time_.QuadPart)
stattdessen sollte diese:
if (t.QuadPart - time_.QuadPart < 0)
Der Grund dafür ist, dass Sie wollen, für die Verpackung in der relativen Zeit, keine absolute Zeit. Relative time wrap (1ull<<63) Zeiteinheiten nach dem Verweis Aufruf von QPC. Absolute Zeit könnte wrap (1ull<<63) Zeiteinheiten nach dem Neustart, aber es wickeln konnte zu jeder anderen Zeit, es fühlte sich wie es, ist nicht definiert.
QPC ist ein wenig verbuggt auf einigen Systemen (ältere RDTSC-basierte QPCs am frühen multicore-CPUs, zum Beispiel) so kann es wünschenswert sein, um kleine, negative Zeit-deltas in etwa so:
if (t.QuadPart - time_.QuadPart < -1000000)
//time wrapEiner tatsächlichen wrap erzeugt eine sehr große negative Zeit-deltas, so dass ' s sicher. Es sollte nicht erforderlich sein, auf modernen Systemen, aber Vertrauen microsoft ist selten eine gute Idee.
...
Jedoch das größere problem es mit der Zeit wickeln ist in der Tatsache, dass
ticks_to_wait
,ticks_passed
, undticks_left
alle sind "int", nicht LARGE_INT oder lang lang, wie Sie sein sollte. Dies macht, dass die meisten von code umbrechen, wenn keine signifikante Zeiträume beteiligt sind - und "erheblich" in diesem Zusammenhang ist Plattform-abhängig ist, kann Sie auf die Größenordnung von 1 Sekunde in ein paar (selten in diesen Tagen) Fällen, oder sogar weniger, auf einige hypothetische zukünftige system.Andere Fragen:
Null ist kein besonderer Wert, und sollten nicht als solche behandelt werden. Meine Vermutung ist, dass der code diese QPC Rückkehr eine Zeit von null mit QPCs Rückgabewert null ist. Der Rückgabewert ist nicht die 64-bit-Zeit übergeben, indem Zeiger, es ist die BOOL, dass QPC tatsächlich gibt.
Sich auch, dass die Schleife Sleep(0) ist dumm - es scheint gestimmt zu haben, funktionieren nur auf einer bestimmten Ebene des Anstoßes und ein bestimmtes pro-thread-CPU-Leistung. Wenn Sie die Auflösung brauchen, das ist eine schreckliche Idee, und wenn Sie nicht brauchen, die Auflösung dann, dass die gesamte Funktion sollte nur einen einzigen Anruf, um zu Schlafen.