游戏计时和多核处理器

由于当今计算机中的电源管理技术越来越常见,一种用于获取高分辨率 CPU 计时的方法,RDTSC 指令可能无法再按预期工作。 本文建议使用 Windows API QueryPerformanceCounterQueryPerformanceFrequency获取高分辨率 CPU 计时的更准确、更可靠的解决方案。

背景

自 x86 P5 指令集引入以来,许多游戏开发者都利用读取时间戳计数器、RDTSC 指令来执行高分辨率计时。 Windows 多媒体计时器对于声音和视频处理足够精确,但帧时间为十几毫秒或更少,它们没有足够的分辨率来提供增量时间信息。 许多游戏仍然在启动时使用多媒体计时器来建立 CPU 的频率,并且它们使用该频率值来缩放 RDTSC 的结果以获取准确的时间。 由于 RDTSC 的限制,Windows API 通过 QueryPerformanceCounterQueryPerformanceFrequency例程公开了访问此功能的更正确的方法。

使用 RDTSC 进行计时时,存在以下基本问题:

  • 非连续值。 直接使用 RDTSC 假定线程始终在同一处理器上运行。 多处理器和双核系统不能保证内核之间的周期计数器同步。 当与不同时间空闲和还原各种核心的新式电源管理技术相结合时,这种情况会加剧,这会导致核心通常无法同步。 对于应用程序,这通常会导致故障或潜在崩溃,因为线程在处理器之间跳跃,并获取导致大增量、负增量或停止计时的计时值。
  • 专用硬件的可用性。 RDTSC 锁定应用程序向处理器的周期计数器请求的计时信息。 多年来,这是获取高精度计时信息的最佳方式,但较新的主板现在包括专用计时设备,这些设备提供高分辨率计时信息,而没有 RDTSC 的缺点。
  • CPU 频率的可变性。 通常假设 CPU 的频率在程序生存期是固定的。 但是,使用现代电源管理技术,这是一个不正确的假设。 虽然最初仅限于笔记本电脑和其他移动设备,但改变 CPU 频率的技术在许多高端台式电脑中使用:禁用其函数以保持一致的频率通常不能接受用户。

建议

游戏需要准确的计时信息,但还需要以避免与使用 RDTSC 相关的问题的方式实现计时代码。 实现高分辨率计时时,请执行以下步骤:

  1. 使用 QueryPerformanceCounterQueryPerformanceFrequency 而不是 RDTSC。 这些 API 可能使用 RDTSC,但可能会改用主板上的计时设备或提供高质量高分辨率计时信息的一些其他系统服务。 虽然 RDTSC 比 QueryPerformanceCounter快得多,因为后者是 API 调用,但它是一个 API,每个帧可以调用数百次,没有任何明显的影响。 (不过,开发人员应尝试将游戏调用 QueryPerformanceCounter 尽可能少,以避免任何性能损失。

  2. 计算增量时,应固定这些值,以确保计时值中的任何 bug 不会导致崩溃或与时间相关的计算不稳定。 固定范围应从 0(以防止负增量值)到基于预期最低的帧速率的一些合理值。 固定在应用程序的任何调试中都可能很有用,但如果执行性能分析或在某些未优化模式下运行游戏,请务必记住它。

  3. 计算单个线程上的所有计时。 对多个线程(例如,与特定处理器关联的每个线程)的计时计算极大地降低了多核系统的性能。

  4. 使用 Windows API SetThreadAffinityMask将单个线程设置为保留在单个处理器上。 通常,这是主游戏线程。 虽然 QueryPerformanceCounterQueryPerformanceFrequency 通常会针对多个处理器进行调整,但 BIOS 或驱动程序中的 bug 可能会导致这些例程返回不同的值,因为线程从一个处理器移动到另一个处理器。 因此,最好将线程保留在单个处理器上。

    所有其他线程应在不收集自己的计时器数据的情况下运行。 不建议使用工作线程来计算计时,因为这会成为同步瓶颈。 相反,工作线程应从主线程读取时间戳,并且由于工作线程仅读取时间戳,因此无需使用关键节。

  5. 仅调用 QueryPerformanceFrequency 一次,因为系统运行时的频率不会更改。

应用程序兼容性

许多开发人员多年来都对 RDTSC 的行为做出了假设,因此,由于计时实现,一些现有应用程序在具有多个处理器或核心的系统上运行时可能会出现问题。 这些问题通常表现为故障或慢动作运动。 对于不知道电源管理的应用程序,没有简单的补救措施,但存在一个现有的填充码,用于强制应用程序始终在多处理器系统中的单个处理器上运行。

若要创建此填充码,请从 Windows 应用程序兼容性下载Microsoft应用程序兼容性工具包。

使用兼容性管理员(工具包的一部分)创建应用程序的数据库和相关修补程序。 为此数据库创建新的兼容性模式,并选择 SingleProcAffinity 兼容性修补程序,以强制应用程序的所有线程在单个处理器/核心上运行。 通过使用命令行工具 Fixpack.exe(也是工具包的一部分),可以将此数据库转换为可安装包,以便进行安装、测试和分发。

有关使用兼容性管理员的说明,请参阅工具包的文档。 有关使用 Fixpack.exe的语法和示例,请参阅其命令行帮助。

有关面向客户的信息,请参阅Microsoft帮助和支持中的以下知识库文章: