Freigeben über


Spieldauer und Multicore-Prozessoren

Da Energieverwaltungstechnologien in den heutigen Computern häufiger eingesetzt werden, kann eine häufig verwendete Methode zum Abrufen von hochauflösenden CPU-Timings, der RDTSC-Anweisung, nicht mehr wie erwartet funktionieren. In diesem Artikel wird eine genauere, zuverlässige Lösung vorgeschlagen, um hochauflösende CPU-Anzeigedauern mithilfe der Windows-APIs QueryPerformanceCounter und QueryPerformanceFrequencyzu erhalten.

Hintergrund

Seit der Einführung des x86 P5-Anweisungssatzes haben viele Spieleentwickler den Lesezeitstempelzähler, die RDTSC-Anweisung, verwendet, um eine hochauflösende Anzeigedauer auszuführen. Die Windows-Multimediatimer sind präzise genug für die Sound- und Videoverarbeitung, aber mit Framezeiten von einem Dutzend Millisekunden oder weniger haben sie nicht genügend Auflösung, um Delta-Zeitinformationen bereitzustellen. Viele Spiele verwenden beim Start noch einen Multimedia-Timer, um die Häufigkeit der CPU festzulegen, und sie verwenden diesen Frequenzwert, um Ergebnisse von RDTSC zu skalieren, um genaue Zeit zu erhalten. Aufgrund der Einschränkungen von RDTSC stellt die Windows-API die richtige Methode für den Zugriff auf diese Funktionalität über die Routinen von QueryPerformanceCounter und QueryPerformanceFrequencyzur Verfügung.

Diese Verwendung von RDTSC für die Anzeigedauer leidet unter diesen grundlegenden Problemen:

  • Nicht zusammenhängende Werte. Bei der direkten Verwendung von RDTSC wird davon ausgegangen, dass der Thread immer auf demselben Prozessor ausgeführt wird. Multiprozessor- und Dual-Core-Systeme garantieren keine Synchronisierung ihrer Zykluszähler zwischen Kernen. Dies wird verschärft, wenn sie in Kombination mit modernen Energieverwaltungstechnologien kombiniert werden, die verschiedene Kerne zu unterschiedlichen Zeiten leerlauf und wiederherstellen, was dazu führt, dass die Kerne in der Regel nicht mehr synchronisiert werden. Bei einer Anwendung führt dies in der Regel zu Störungen oder potenziellen Abstürzen, wenn der Thread zwischen den Prozessoren springt und Anzeigedauerwerte abruft, die zu großen Deltas, negativen Deltas oder angehaltenen Anzeigedauern führen.
  • Verfügbarkeit dedizierter Hardware. RDTSC sperrt die Anzeigedauerinformationen, die die Anwendung an den Zykluszähler des Prozessors anfordert. Seit vielen Jahren war dies die beste Möglichkeit, präzise Timing-Informationen zu erhalten, aber neuere Motherboards enthalten jetzt dedizierte Timing-Geräte, die hochauflösende Timing-Informationen ohne die Nachteile von RDTSC bieten.
  • Streuung der CPU-Frequenz. Die Annahme wird häufig gemacht, dass die Häufigkeit der CPU für die Lebensdauer des Programms festgelegt ist. Bei modernen Energiemanagementtechnologien ist dies jedoch eine falsche Annahme. Obwohl sie anfänglich auf Laptopcomputer und andere mobile Geräte beschränkt ist, wird die Technologie, die die Häufigkeit der CPU ändert, in vielen High-End-Desktop-PCs verwendet; Die Deaktivierung der Funktion zur Beibehaltung einer konsistenten Häufigkeit ist in der Regel für Benutzer nicht akzeptabel.

Empfehlungen

Spiele benötigen genaue Zeitangaben, aber Sie müssen auch Timing-Code auf eine Weise implementieren, die die Mit der Verwendung von RDTSC verbundenen Probleme vermeidet. Wenn Sie die Zeitdauer 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 Timing-Gerät auf der Hauptplatine oder andere Systemdienste, die qualitativ hochwertige Hochauflösende Timing-Informationen bereitstellen. Während RDTSC viel schneller ist als QueryPerformanceCounter, da letztere ein API-Aufruf ist, handelt es sich um eine API, die mehrere hundert Mal pro Frame aufgerufen werden kann, ohne spürbare Auswirkungen zu haben. (Dennoch sollten Entwickler versuchen, ihre Spiele QueryPerformanceCounter so wenig wie möglich aufzurufen, um leistungseinbußen zu vermeiden.)

  2. Beim Berechnen von Deltas sollten die Werte eingeklemmt werden, um sicherzustellen, dass fehler in den Zeitwerten 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 sein. Die Klammerung ist wahrscheinlich bei jedem Debuggen Ihrer Anwendung nützlich, aber achten Sie darauf, sie zu berücksichtigen, wenn Sie leistungsanalyse oder das Spiel in einem nicht optimierten Modus ausführen.

  3. Berechnen Sie alle Anzeigedauern für einen einzelnen Thread. Die Berechnung der Anzeigedauer auf mehreren Threads , z. B. bei jedem Thread, der einem bestimmten Prozessor zugeordnet ist, reduziert die Leistung von Multi-Core-Systemen erheblich.

  4. Legen Sie fest, dass dieser einzelne Thread auf einem einzelnen Prozessor verbleibt, indem Sie die Windows-API SetThreadAffinityMaskverwenden. In der Regel ist dies der Hauptthread des Spiels. 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 eigene Zeitgeberdaten zu sammeln. Es wird nicht empfohlen, einen Workerthread zum Berechnen der Anzeigedauer zu verwenden, da dies zu einem Synchronisierungsengpässe 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 während der Ausführung des Systems nicht ändert.

Anwendungskompatibilität

Viele Entwickler haben über viele Jahre Annahmen über das Verhalten von RDTSC gemacht, daher ist es wahrscheinlich, dass einige vorhandene Anwendungen Probleme verursachen, wenn sie auf einem System mit mehreren Prozessoren oder Kernen ausgeführt werden, aufgrund der Timing-Implementierung. Diese Probleme manifestieren sich in der Regel als Schlitz- oder Slow-Motion-Bewegung. Es gibt kein einfaches Mittel für Anwendungen, die sich der Energieverwaltung nicht bewusst sind, aber es gibt einen vorhandenen Shim, um eine Anwendung zu erzwingen, immer auf einem einzelnen Prozessor in einem Multiprozessorsystem ausgeführt zu werden.

Um diesen Shim zu erstellen, laden Sie das Microsoft Application Compatibility Toolkit aus Windows Application Compatibilityherunter.

Erstellen Sie mithilfe des Kompatibilitätsadministrators, Teil des Toolkits, eine Datenbank Ihrer Anwendung und zugehörige Fixes. Erstellen Sie einen neuen Kompatibilitätsmodus für diese Datenbank, und wählen Sie die Kompatibilitätskorrektur SingleProcAffinity- aus, um alle Threads der Anwendung auf einem einzelnen Prozessor/Core auszuführen. Mithilfe des Befehlszeilentools Fixpack.exe (auch 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. Eine Syntax und Beispiele für die Verwendung von Fixpack.exefinden Sie in der Befehlszeilenhilfe.

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