Zeitliche Steuerung von Spielen und Multi-Core-Prozessoren

Da Energieverwaltungstechnologien auf den heutigen Computern immer häufiger verwendet werden, funktioniert die RDTSC-Anweisung, eine häufig verwendete Methode zum Abrufen von hochauflösenden CPU-Zeitsteuerungen, möglicherweise nicht mehr wie erwartet. In diesem Artikel wird eine genauere, zuverlässigere Lösung vorgeschlagen, um mithilfe der Windows-APIs QueryPerformanceCounter und QueryPerformanceFrequencyhigh-resolution cpu timings zu erhalten.

Hintergrund

Seit der Einführung des x86 P5-Anweisungssets haben viele Spieleentwickler den Zeitstempelzähler für Lesezeit, die RDTSC-Anweisung, verwendet, um eine zeitbasierte Zeitsteuerung mit hoher Auflösung durchzuführen. Die Windows Multimedia-Timer sind präzise genug für die Audio- und Videoverarbeitung, aber bei Framezeiten von einem Dutzend Millisekunden oder weniger haben sie nicht genügend Auflösung, um Deltazeitinformationen zu liefern. Viele Spiele verwenden immer noch einen Multimedia-Timer beim Start, um die Häufigkeit der CPU zu festlegen, und sie verwenden diesen Frequency-Wert, um Ergebnisse von RDTSC zu skalieren, um eine genaue Zeit zu erhalten. Aufgrund der Einschränkungen von RDTSC bietet die Windows-API die korrektere Möglichkeit, über die Routinen von QueryPerformanceCounter und QueryPerformanceFrequencyauf diese Funktionalität zuzugreifen.

Diese Verwendung von RDTSC für die zeitlichen Steuerungen ist von folgenden grundlegenden Problemen betroffen:

  • Diskontinuitätswerte. Bei der direkten Verwendung von RDTSC wird davon ausgegangen, dass der Thread immer auf demselben Prozessor ausgeführt wird. Systeme mit mehreren Prozessoren und dualen Kernen garantieren nicht die Synchronisierung ihrer Zykluszähler zwischen Kernen. Dies wird in Kombination mit modernen Energieverwaltungstechnologien verstärkt, die verschiedene Kerne zu unterschiedlichen Zeiten leeren und wiederherstellen, was dazu führt, dass die Kerne in der Regel nicht synchronisiert werden. Für eine Anwendung führt dies in der Regel zu Störungen oder potenziellen Abstürzen, wenn der Thread zwischen den Prozessoren springt und Zeitsteuerungswerte erhält, die zu großen Deltas, negativen Deltas oder angehaltener Zeitsteuerung führen.
  • Verfügbarkeit dedizierter Hardware. RDTSC sperrt die Zeitsteuerungsinformationen, die die Anwendung an den Zykluszähler des Prozessors an fordert. Viele Jahre lang war dies die beste Möglichkeit, um Zeitsteuerungsinformationen mit hoher Genauigkeit zu erhalten, aber neuere Hauptplatinen enthalten jetzt dedizierte Zeitsteuerungsgeräte, die hochwertige Zeitsteuerungsinformationen ohne die Nachteile von RDTSC bereitstellen.
  • Variabilität der CPU-Häufigkeit. Häufig wird davon ausgegangen, dass die Cpu-Häufigkeit für die Lebensdauer des Programms festgelegt ist. Bei modernen Energieverwaltungstechnologien ist dies jedoch eine falsche Annahme. Obwohl anfänglich auf Laptopcomputer und andere mobile Geräte beschränkt, wird technologie, die die Cpu-Häufigkeit ändert, in vielen High-End-Desktop-PCs verwendet. Das Deaktivieren der Funktion zur Aufrechterhaltung einer konsistenten Häufigkeit ist für Benutzer in der Regel nicht akzeptabel.

Empfehlungen

Spiele benötigen genaue Zeitsteuerungsinformationen, aber Sie müssen auch Zeitsteuerungscode so implementieren, dass die Probleme im Zusammenhang mit der Verwendung von RDTSC vermieden werden. Wenn Sie eine zeitbasierte Zeitsteuerung mit hoher Auflösung implementieren, führen Sie die folgenden Schritte aus:

  1. Verwenden Sie QueryPerformanceCounter und QueryPerformanceFrequency anstelle von RDTSC. Diese APIs verwenden möglicherweise RDTSC, verwenden aber stattdessen ein Zeitsteuerungsgeräte auf der Hauptplatine oder einige andere Systemdienste, die qualitativ hochwertige, hochauflösende Zeitsteuerungsinformationen bereitstellen. RDTSC ist zwar viel schneller als QueryPerformanceCounter, da es sich bei letzterem um einen API-Aufruf handelt, aber es handelt sich um eine API, die mehrmals pro Frame aufgerufen werden kann, ohne dass es zu spürbaren Auswirkungen kommt. (Dennoch sollten Entwickler versuchen, ihre Spiele so wenig wie möglich queryPerformanceCounter aufrufen zu lassen, um Leistungssentspricht zu vermeiden.)

  2. Beim Berechnen von Deltas sollten die Werte zusammengeklammert werden, um sicherzustellen, dass Fehler in den Zeitsteuerungswerten keine Abstürze oder instabile zeitbezogene Berechnungen verursachen. Der Klammerbereich sollte von 0 (um negative Deltawerte zu verhindern) bis zu einem angemessenen Wert basierend auf der niedrigsten erwarteten Framerate liegen. Das Anschließen ist wahrscheinlich bei jedem Debuggen Ihrer Anwendung nützlich, aber denken Sie daran, wenn Sie eine Leistungsanalyse durchführen oder das Spiel in einem nicht optimierten Modus ausführen.

  3. Berechnen sie alle Zeitsteuerungen für einen einzelnen Thread. Die Berechnung der Zeitsteuerung für mehrere Threads , z. B. mit jedem Thread, der einem bestimmten Prozessor zugeordnet ist, verringert die Leistung von Systemen mit mehreren Kernen deutlich.

  4. Legen Sie fest, dass dieser einzelne Thread auf einem einzelnen Prozessor verbleibt, indem Windows API SetThreadAffinityMask verwendet wird. In der Regel ist dies der Hauptspielthread. Während QueryPerformanceCounter und QueryPerformanceFrequency in der Regel für mehrere Prozessoren angepasst werden, können Fehler im BIOS oder treiber dazu führen, dass diese Routinen unterschiedliche Werte zurückgeben, wenn der Thread von einem Prozessor zu einem anderen wechselt. Daher ist es am besten, den Thread auf einem einzelnen Prozessor zu halten.

    Alle anderen Threads sollten ausgeführt werden, ohne ihre eigenen Timerdaten zu erfassen. Es wird nicht empfohlen, einen Arbeitsthread zum Berechnen der Zeitsteuerung zu verwenden, da dies zu einem Synchronisierungsengpass wird. Stattdessen sollten Arbeitsthreads Zeitstempel aus dem Hauptthread lesen, und da die Arbeitsthreads nur Zeitstempel lesen, müssen keine kritischen Abschnitte verwendet werden.

  5. Rufen Sie QueryPerformanceFrequency nur einmal auf, da sich die Häufigkeit nicht ändert, während das System ausgeführt wird.

Anwendungskompatibilität

Viele Entwickler haben über viele Jahre Annahmen über das Verhalten von RDTSC getroffen, sodass es sehr wahrscheinlich ist, dass einige vorhandene Anwendungen probleme auftreten, wenn sie auf einem System mit mehreren Prozessoren oder Kernen aufgrund der zeitlichen Implementierung ausgeführt werden. Diese Probleme manifestieren sich in der Regel als Störung oder Langsamkeitsbewegung. Es gibt keine einfache Lösung für Anwendungen, die die Energieverwaltung nicht kennen, aber es gibt einen vorhandenen Shim, um zu erzwingen, dass eine Anwendung immer auf einem einzelnen Prozessor in einem Multiprozessorsystem ausgeführt wird.

Um diesen Shim zu erstellen, laden Sie das Microsoft Application Compatibility Toolkit unter Windows Application Compatibility herunter.

Erstellen Sie mithilfe des Kompatibilitätsadministrators, der Teil des Toolkits ist, eine Datenbank Ihrer Anwendung und zugehörige Korrekturen. Erstellen Sie einen neuen Kompatibilitätsmodus für diese Datenbank, und wählen Sie den Kompatibilitätsfix SingleProcAffinity aus, um die Ausführung aller Threads der Anwendung auf einem einzelnen Prozessor/Kern zu erzwingen. Mithilfe des Befehlszeilentools Fixpack.exe (ebenfalls Teil des Toolkits) können Sie diese Datenbank in ein installierbares Paket für Installation, Tests und Verteilung konvertieren.

Anweisungen zur Verwendung des Kompatibilitätsadministrators finden Sie in der Dokumentation des Toolkits. Syntax und Beispiele für die Verwendung von Fixpack.exe finden Sie in der Befehlszeilenhilfe.

Kundenorientierte Informationen finden Sie in den folgenden Knowledge Base-Artikeln unter Microsoft-Hilfe und -Support:

  • Programme, die die QueryPerformanceCounter-Funktion verwenden, können in Windows Server 2003 und in Windows XP (Artikel 895980) eine schlechte Leistung erzielen.