游戏计时和多核处理器
随着电源管理技术在当今计算机中变得越来越普遍,一种用于获取高分辨率 CPU 计时的常用方法 RDTSC 指令可能不再按预期工作。 本文建议使用 Windows API QueryPerformanceCounter 和 QueryPerformanceFrequency 获取高分辨率 CPU 计时的更准确、更可靠的解决方案。
背景
自从引入 x86 P5 指令集以来,许多游戏开发者都利用读取时间戳计数器(RDTSC 指令)来执行高分辨率计时。 Windows 多媒体计时器对于声音和视频处理来说足够精确,但帧时间在十几毫秒或更短的情况下,它们没有足够的分辨率来提供增量时间信息。 许多游戏在启动时仍使用多媒体计时器来建立 CPU 的频率,并且它们使用该频率值来缩放 RDTSC 的结果以获取准确的时间。 由于 RDTSC 的限制,Windows API 通过 QueryPerformanceCounter 和 QueryPerformanceFrequency 的例程公开了访问此功能的更正确方法。
使用 RDTSC 进行计时存在以下基本问题:
- 非连续值。 使用 RDTSC 直接假定线程始终在同一处理器上运行。 多处理器和双核系统不保证内核之间的周期计数器同步。 与在不同时间空闲和还原各种核心的新式电源管理技术结合使用时,这种情况会加剧,这通常会导致内核不同步。 对于应用程序,这通常会导致故障或潜在的崩溃,因为线程在处理器之间跳跃,并获取导致较大的增量、负增量或停止计时的计时值。
- 专用硬件的可用性。 RDTSC 锁定应用程序向处理器的周期计数器请求的计时信息。 多年来,这是获取高精度计时信息的最佳方式,但较新的主板现在包括专用计时设备,这些设备提供高分辨率计时信息,而没有 RDTSC 的缺点。
- CPU 频率的可变性。 通常假设 CPU 的频率在程序的生命周期内是固定的。 但是,对于新式电源管理技术,这是一个错误的假设。 虽然最初仅限于笔记本电脑和其他移动设备,但改变 CPU 频率的技术在许多高端台式电脑中使用:用户通常不接受禁用其功能以保持一致的频率。
建议
游戏需要准确的计时信息,但你还需要以避免与使用 RDTSC 相关的问题的方式实现计时代码。 实现高分辨率计时时,请执行以下步骤:
使用 QueryPerformanceCounter 和 QueryPerformanceFrequency 而不是 RDTSC。 这些 API 可能使用 RDTSC,但可能会改用主板上的计时设备或提供高质量高分辨率计时信息的其他一些系统服务。 虽然 RDTSC 比 QueryPerformanceCounter 快得多,但由于后者是 API 调用,因此它是一个 API,每个帧可以调用数百次,而不会产生任何明显的影响。 (不过,开发人员应尽量少地尝试让游戏调用 QueryPerformanceCounter ,以避免任何性能损失。)
计算增量时,应限制值,以确保计时值中的任何 bug 不会导致崩溃或不稳定的时间相关计算。 固定范围应从 0 (,以防止负增量值) 为基于最低预期帧速率的合理值。 固定在应用程序的任何调试中都可能很有用,但如果执行性能分析或在某个未优化模式下运行游戏,请务必记住这一点。
计算单个线程上的所有计时。 对多个线程(例如,每个线程与特定处理器关联的)的计时计算会大大降低多核系统的性能。
使用 Windows API SetThreadAffinityMask 将单个线程设置为保留在单个处理器上。 通常,这是main游戏线程。 虽然 QueryPerformanceCounter 和 QueryPerformanceFrequency 通常会针对多个处理器进行调整,但 BIOS 或驱动程序中的 bug 可能会导致这些例程在线程从一个处理器移动到另一个处理器时返回不同的值。 因此,最好将线程保留在单个处理器上。
所有其他线程应在不收集自己的计时器数据的情况下运行。 不建议使用工作线程来计算计时,因为这样会成为同步瓶颈。 相反,工作线程应从main线程读取时间戳,并且由于工作线程仅读取时间戳,因此无需使用关键部分。
调用 QueryPerformanceFrequency 仅一次,因为系统运行时频率不会更改。
应用程序兼容性
多年来,许多开发人员都对 RDTSC 的行为做出了假设,因此,由于计时实现,在具有多个处理器或内核的系统上运行时,一些现有应用程序很有可能会出现问题。 这些问题通常表现为故障或运动缓慢。 对于不知道电源管理的应用程序,没有简单的补救措施,但存在一个现有的填充码,用于强制应用程序始终在多处理器系统中的单个处理器上运行。
若要创建此填充码,请从 Windows 应用程序兼容性下载 Microsoft 应用程序兼容性工具包。
使用工具包的一部分兼容性管理员,创建应用程序的数据库和关联的修补程序。 为此数据库创建新的兼容模式,并选择兼容性修补程序 SingleProcAffinity 以强制应用程序的所有线程在单个处理器/内核上运行。 通过使用命令行工具Fixpack.exe (也是工具包) 的一部分,可以将此数据库转换为可安装包,以便进行安装、测试和分发。
有关使用兼容性管理员的说明,请参阅工具包的文档。 有关使用 Fixpack.exe 的语法和示例,请参阅其命令行帮助。
有关面向客户的信息,请参阅 Microsoft 帮助和支持中的以下知识库文章: