Windows 10 下的多核处理器上的 QueryPerformanceCounter 行为不稳定
QueryPerformanceCounter on multi-core processor under Windows 10 behaves erratically
在 Windows 下,我的应用程序使用 QueryPerformanceCounter
(和 QueryPerformanceFrequency
)来执行 "high resolution" 时间戳。
自 Windows 10(目前仅在 Intel i7 处理器上测试过)以来,我们观察到 QueryPerformanceCounter
返回值的不稳定行为。
有时,调用返回的值会跳得很远,然后又回到它以前的值。
感觉好像线程已经从一个核心移动到另一个核心,并且在一段时间内返回了不同的计数器值(没有证据,只是一种直觉)。
这在 XP 或 7 下从未观察到(没有关于 Vista、8 或 8.1 的数据)。
一个 "simple" 解决方法是使用 BCDEdit 启用 UsePlatformClock 启动选项(这使得一切都正常运行)。
我知道潜在的优势 GetSystemTimePreciseAsFileTime
但由于我们仍然支持 7,所以这不是一个确切的选择,除非我们为不同的操作系统编写完全不同的代码,而我们真的不想这样做。
在 Windows 10 下 observed/explained 有过这种行为吗?
我需要更多关于您的代码的知识,但让我强调一些来自 MSDN 的内容:
When computing deltas, the values [from QueryPerformanceCounter] should be clamped to ensure that any bugs in the timing values do not cause crashes or unstable time-related computations.
尤其是这个:
Set that single thread to remain on a single processor by using the Windows API SetThreadAffinityMask ... While QueryPerformanceCounter and QueryPerformanceFrequency typically adjust for multiple processors, bugs in the BIOS or drivers may result in these routines returning different values as the thread moves from one processor to another. So, it's best to keep the thread on a single processor.
您的案例可能利用了其中一个漏洞。简而言之:
- 你应该总是从一个线程查询时间戳(设置相同的 CPU affinity 以确保它不会改变)并从任何其他线程读取该值(只是一个互锁读取,不需要花哨的同步)。
- 钳制计算出的增量(至少要确保它不是负数)...
备注:
如果可能,QueryPerformanceCounter()
使用 TSC(参见 MSDN)。同步 TSC 的算法(如果可用,在您的情况下应该是)从 Windows 7 大幅更改为 Windows 8,但请注意:
With the advent of multi-core/hyper-threaded CPUs, systems with multiple CPUs, and hibernating operating systems, the TSC cannot be relied upon to provide accurate results — unless great care is taken to correct the possible flaws: rate of tick and whether all cores (processors) have identical values in their time-keeping registers. There is no promise that the timestamp counters of multiple CPUs on a single motherboard will be synchronized. Therefore, a program can get reliable results only by limiting itself to run on one specific CPU.
然后,即使理论上 QPC 是单调的,您也必须始终从同一个线程调用它以确保这一点。
另一个注意事项:如果同步是通过软件进行的,您可以从 Intel 文档中了解到:
...It may be difficult for software to do this in a way than ensures that all logical processors will have the same value for the TSC at a given point in time...
编辑:如果您的应用程序是多线程的并且您不能(或不想)设置 CPU affinity(特别是如果您需要精确的时间戳以在线程之间具有不同步的值为代价)然后您可以在 Win8(或更高版本)上使用 运行 时使用 GetSystemTimePreciseAsFileTime()
并回退到 Win7 的 timeGetTime()
(在您设置粒度之后使用 timeBeginPeriod(1)
到 1 ms,并假设 1 ms 的分辨率就足够了)。一个非常有趣的阅读:The Windows Timestamp Project.
编辑2:OP直接推荐!这在适用时(因为它是系统设置,而不是您的应用程序的本地设置)可能是一个简单的解决方法。您可以使用 bcdedit 强制 QPC 使用 HPET 而不是 TSC(参见 MSDN)。延迟和分辨率应该更差,但它本质上 安全 不受上述问题的影响。
在 Windows 下,我的应用程序使用 QueryPerformanceCounter
(和 QueryPerformanceFrequency
)来执行 "high resolution" 时间戳。
自 Windows 10(目前仅在 Intel i7 处理器上测试过)以来,我们观察到 QueryPerformanceCounter
返回值的不稳定行为。
有时,调用返回的值会跳得很远,然后又回到它以前的值。
感觉好像线程已经从一个核心移动到另一个核心,并且在一段时间内返回了不同的计数器值(没有证据,只是一种直觉)。
这在 XP 或 7 下从未观察到(没有关于 Vista、8 或 8.1 的数据)。
一个 "simple" 解决方法是使用 BCDEdit 启用 UsePlatformClock 启动选项(这使得一切都正常运行)。
我知道潜在的优势 GetSystemTimePreciseAsFileTime
但由于我们仍然支持 7,所以这不是一个确切的选择,除非我们为不同的操作系统编写完全不同的代码,而我们真的不想这样做。
在 Windows 10 下 observed/explained 有过这种行为吗?
我需要更多关于您的代码的知识,但让我强调一些来自 MSDN 的内容:
When computing deltas, the values [from QueryPerformanceCounter] should be clamped to ensure that any bugs in the timing values do not cause crashes or unstable time-related computations.
尤其是这个:
Set that single thread to remain on a single processor by using the Windows API SetThreadAffinityMask ... While QueryPerformanceCounter and QueryPerformanceFrequency typically adjust for multiple processors, bugs in the BIOS or drivers may result in these routines returning different values as the thread moves from one processor to another. So, it's best to keep the thread on a single processor.
您的案例可能利用了其中一个漏洞。简而言之:
- 你应该总是从一个线程查询时间戳(设置相同的 CPU affinity 以确保它不会改变)并从任何其他线程读取该值(只是一个互锁读取,不需要花哨的同步)。
- 钳制计算出的增量(至少要确保它不是负数)...
备注:
如果可能,QueryPerformanceCounter()
使用 TSC(参见 MSDN)。同步 TSC 的算法(如果可用,在您的情况下应该是)从 Windows 7 大幅更改为 Windows 8,但请注意:
With the advent of multi-core/hyper-threaded CPUs, systems with multiple CPUs, and hibernating operating systems, the TSC cannot be relied upon to provide accurate results — unless great care is taken to correct the possible flaws: rate of tick and whether all cores (processors) have identical values in their time-keeping registers. There is no promise that the timestamp counters of multiple CPUs on a single motherboard will be synchronized. Therefore, a program can get reliable results only by limiting itself to run on one specific CPU.
然后,即使理论上 QPC 是单调的,您也必须始终从同一个线程调用它以确保这一点。
另一个注意事项:如果同步是通过软件进行的,您可以从 Intel 文档中了解到:
...It may be difficult for software to do this in a way than ensures that all logical processors will have the same value for the TSC at a given point in time...
编辑:如果您的应用程序是多线程的并且您不能(或不想)设置 CPU affinity(特别是如果您需要精确的时间戳以在线程之间具有不同步的值为代价)然后您可以在 Win8(或更高版本)上使用 运行 时使用 GetSystemTimePreciseAsFileTime()
并回退到 Win7 的 timeGetTime()
(在您设置粒度之后使用 timeBeginPeriod(1)
到 1 ms,并假设 1 ms 的分辨率就足够了)。一个非常有趣的阅读:The Windows Timestamp Project.
编辑2:OP直接推荐!这在适用时(因为它是系统设置,而不是您的应用程序的本地设置)可能是一个简单的解决方法。您可以使用 bcdedit 强制 QPC 使用 HPET 而不是 TSC(参见 MSDN)。延迟和分辨率应该更差,但它本质上 安全 不受上述问题的影响。