在Windows下,我的应用程序利用QueryPerformanceCounter
(和QueryPerformanceFrequency
)来执行"高分辨率"时间戳。
自 Windows 10(到目前为止仅在英特尔 i7 处理器上测试)以来,我们观察到返回的值中存在不稳定的行为QueryPerformanceCounter
. 有时,调用返回的值会跳得很远,然后回到其以前的值。 感觉就像线程从一个核心移动到另一个核心,并在一段时间内返回了不同的计数器值(没有证据,只是直觉)。
这在XP或7下从未观察到过(没有关于Vista,8或8.1的数据)。
一个"简单"的解决方法是使用 BCDEdit 启用 UsePlatformClock 启动操作程序(这使得一切正常)。
我知道潜在的优越GetSystemTimePreciseAsFileTime
但由于我们仍然支持 7,除非我们为不同的操作系统编写不同的代码,否则这不是一个选择,我们真的不想这样做。
在 Windows 10 下是否观察到/解释了这种行为?
我需要更多关于您的代码的知识,但让我强调 MSDN 中的几件事:
计算增量时,应固定[来自 QueryPerformanceCounter] 的值,以确保计时值中的任何错误不会导致崩溃或与时间相关的计算不稳定。
尤其是这个:
将单个线程设置为保留在单个处理器上,方法是使用 Windows API SetThreadAffinityMask ...虽然 QueryPerformanceCounter 和 QueryPerformanceFrequency 通常会针对多个处理器进行调整,但BIOS 或驱动程序中的错误可能会导致这些例程在线程从一个处理器移动到另一个处理器时返回不同的值。因此,最好将线程保留在单个处理器上。
您的案例可能会利用其中一个错误。总之:
- 您应该始终从一个线程查询时间戳(设置相同的 CPU 关联以确保它不会更改)并从任何其他线程读取该值(只是一个联锁读取,不需要花哨的同步)。
- 夹紧计算出的增量(至少要确保它不是负数)...
笔记:
如果可能,QueryPerformanceCounter()
使用 TSC(请参阅 MSDN)。同步TSC的算法(如果可用,在您的情况下应该是)从Windows 7到Windows 8发生了巨大变化,但请注意:
随着多核/超线程 CPU、具有多个 CPU 的系统以及休眠操作系统的出现,TSC 不能提供准确的结果——除非非常小心地纠正可能的缺陷:滴答速率以及所有内核(处理器)是否在其计时寄存器中具有相同的值。不承诺单个主板上多个CPU的时间戳计数器将同步。因此,程序只能通过将自身限制在一个特定的CPU上运行来获得可靠的结果。
然后,即使理论上QPC是单调的,也必须始终从同一线程调用它以确保这一点。
另一个注意事项:如果同步是通过软件进行的,您可以从英特尔文档中读到:
。软件可能很难以某种方式做到这一点,而不是确保所有逻辑处理器在给定时间点具有相同的 TSC 值...
编辑:如果您的应用程序是多线程的,并且您不能(或者您不想)设置 CPU 关联(特别是如果您需要精确的时间戳,但代价是在线程之间具有不同步的值),那么您可以在 Win8(或更高版本)上运行时使用GetSystemTimePreciseAsFileTime()
并回退到 Win7 的timeGetTime()
(在使用timeBeginPeriod(1)
将粒度设置为 1 毫秒并假设 1 毫秒分辨率就足够了)。一个非常有趣的读物:Windows 时间戳项目。
编辑2:OP直接建议!如果适用(因为它是系统设置,而不是应用程序的本地设置),这可能是一个简单的解决方法。您可以使用 bcdedit 强制 QPC 使用 HPET 而不是 TSC(请参阅 MSDN)。延迟和分辨率应该更差,但从本质上讲,它对上述问题来说是安全的。