引言
游戏程序中有很多需要用到时间的地方,往往会通过windows API来获取时间。先前写过一篇文章是关于时间同步的:网络游戏中的(低精度)时间同步,当需求更高精度的时间同步时,就需要QueryPerformanceCounter这样的API,而QueryPerformanceCounter的使用有一些隐含的陷阱需要注意。
关于QueryPerformanceCounter
官方解释:https://msdn.microsoft.com/zh-cn/ms644904,用于高精度计时器时间读取,重点是执行成功返回非0值,精度是<1us的。
多核CPU采用QueryPerformanceCounter的问题
程序中通常使用时间差定时地调用某种接口的情况,而时间差的使用有一个隐含因素,即时间是顺序累加的,当然我们通常的时间当然是累加的,不会出现停滞甚至倒转,而QueryPerformanceCounter的运行情况是依赖于CPU的,当CPU是多核时,在某一线程内调用QueryPerformanceCounter,线程会切换于不同的核心之间,这时候QueryPerformanceCounter返回值是不确定的,或者说这时候的计时器并不能保证是顺序累加的,相应地,当使用时间差时会出现负数或者0的情况,这显然不符合开发者的预期。
如何在多核CPU的环境下使用QueryPerformanceCounter
目前多核的CPU已经飞入寻常百姓人家,因而作为开发人员,不得不面对在多核CPU的机器上使用QueryPerformanceCounter的情况。当我们需要在某一进程中获取时间,需要将该线程绑定在某一固定的核心上,这样获取的高精度计时器才是可靠的。通过SetThreadAffinityMask可以实现这一目的。举个栗子:
参考资料:
http://bbs.csdn.net/topics/310177138
https://msdn.microsoft.com/en-us/library/ee417693%28v=vs.85%29.aspx
http://blog.csdn.net/hunter8777/article/details/6204719
HANDLE thread = GetCurrentThread();
// Set affinity to the first core
DWORD_PTR oldMask = SetThreadAffinityMask(thread, GTimerMask );
// Query the timer
QueryPerformanceCounter(&mStartTime);
mStartTick = GetTickCount();
// Reset affinity
SetThreadAffinityMask(thread, oldMask);
使用前,需要重置GTimerMask:void appResetTimerMask()
{
// Get the current process core mask
ULONG_PTR proMask;
ULONG_PTR sysMask;
GetProcessAffinityMask(GetCurrentProcess(), &procMask, &sysMask);
// If procMask is 0, consider there is only one core available
// (using 0 as procMask will cause an infinite loop below)
if (procMask == 0)
procMask = 1;
// Find the lowest core that this process uses
if (GTimerMask == 0)
{
GTimeMask = 1;
while ((GTimerMask & procMask) == 0)
{
GTimerMask <<= 1;
}
}
}
完整的使用方法:
inline DOUBLE appSeconds()
{
LARGE_INTEGER Cycles;
HANDLE thread = NULL;
ULONG_PTR oldMask = 0;
if (GEnableTimerAffinityMask) //GEnableTimerAffinityMask为TRUE时需要将线程绑定到固定核心
{
thread = GetCurrentThread();
if (GTimerMask == 0) //GTimerMask默认值为0
{
appResetTimerMask();
}
oldMask = SetThreadAffinityMask(thread, GTimerMask);
}
QueryPerformanceCounter(&Cycles);
if (GEnableTimerAffinityMask)
{
SetThreadAffinityMask(thread, oldMask);
}
return Cycles.QuadPart * GSecondsPerCycle + 16777216.0;
}
参考资料:
http://bbs.csdn.net/topics/310177138
https://msdn.microsoft.com/en-us/library/ee417693%28v=vs.85%29.aspx
http://blog.csdn.net/hunter8777/article/details/6204719