Windows 7计时function – 如何正确使用GetSystemTimeAdjustment?

我在Windows 7上使用GetSystemTimeAdjustment函数进行了一些testing,并得到了一些我无法解释的有趣结果。 按照我的理解,如果系统时间周期性地同步,那么这个方法应该返回,如果是,在哪个时间间隔和哪个增量更新( 参见MSDN上的GetSystemTimeAdjustment函数 )。

从这我跟着,如果我查询系统时间例如使用GetSystemTimeAsFileTime重复我应该得到没有改变(系统时钟还没有更新),或者一个改变是由GetSystemTimeAdjustment检索增量的倍数。 问题一:这个假设是否正确?

现在考虑下面的testing代码:

 #include <windows.h> #include <iostream> #include <iomanip> int main() { FILETIME fileStart; GetSystemTimeAsFileTime(&fileStart); ULARGE_INTEGER start; start.HighPart = fileStart.dwHighDateTime; start.LowPart = fileStart.dwLowDateTime; for (int i=20; i>0; --i) { FILETIME timeStamp1; ULARGE_INTEGER ts1; GetSystemTimeAsFileTime(&timeStamp1); ts1.HighPart = timeStamp1.dwHighDateTime; ts1.LowPart = timeStamp1.dwLowDateTime; std::cout << "Timestamp: " << std::setprecision(20) << (double)(ts1.QuadPart - start.QuadPart) / 10000000 << std::endl; } DWORD dwTimeAdjustment = 0, dwTimeIncrement = 0, dwClockTick; BOOL fAdjustmentDisabled = TRUE; GetSystemTimeAdjustment(&dwTimeAdjustment, &dwTimeIncrement, &fAdjustmentDisabled); std::cout << "\nTime Adjustment disabled: " << fAdjustmentDisabled << "\nTime Adjustment: " << (double)dwTimeAdjustment/10000000 << "\nTime Increment: " << (double)dwTimeIncrement/10000000 << std::endl; } 

它在一个循环中需要20个时间戳并将其打印到控制台。 最后打印系统时钟更新的增量。 我期望在循环中打印的时间戳之间的差异为0或这个增量的倍数。 不过,我得到这样的结果:

 Timestamp: 0 Timestamp: 0.0025000000000000001 Timestamp: 0.0074999999999999997 Timestamp: 0.01 Timestamp: 0.012500000000000001 Timestamp: 0.014999999999999999 Timestamp: 0.017500000000000002 Timestamp: 0.022499999999999999 Timestamp: 0.025000000000000001 Timestamp: 0.0275 Timestamp: 0.029999999999999999 Timestamp: 0.032500000000000001 Timestamp: 0.035000000000000003 Timestamp: 0.040000000000000001 Timestamp: 0.042500000000000003 Timestamp: 0.044999999999999998 Timestamp: 0.050000000000000003 Timestamp: 0.052499999999999998 Timestamp: 0.055 Timestamp: 0.057500000000000002 Time Adjustment disabled: 0 Time Adjustment: 0.0156001 Time Increment: 0.0156001 

所以看起来系统时间用GetSystemTimeAdjustment返回的时间间隔大约为0.0025秒而不是0.0156秒。

问题二:这是什么原因?

GetSystemTimeAsFileTime API以文件时间格式提供对系统挂钟的访问。

一个64位的FILETIME结构接收系统时间为100ns单位的FILETIME,从1601年1月1日起已经过期。对GetSystemTimeAsFileTime的调用通常需要10ns到15ns。

为了调查此API提供的系统时间的真实精度,需要讨论随时间值而来的粒度。 换句话说:系统时间多久更新一次? 第一个估计是由隐藏的API调用提供的:

 NTSTATUS NtQueryTimerResolution(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution, OUT PULONG ActualResolution); 

NtQueryTimerResolution由本地Windows NT库NTDLL.DLL导出。 此调用报告的ActualResolution表示系统时间的更新周期,单位为100 ns,不一定匹配中断周期。 价值取决于硬件平台。 对于ActualResolution ,通用硬件平台报告156,250或100,144 ; 较旧的平台可能会报告更大的数字; 较新的系统,特别是当HPET (高精度事件定时器)或constant/invariant TSC支持时,实际分辨率可以返回156,001

这是控制系统的心跳之一。 最小分辨率和实际分辨率与多媒体定时器configuration相关。

ActualResolution可以通过使用API​​调用来设置

 NTSTATUS NtSetTimerResolution(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution); 

或通过多媒体定时器接口

 MMRESULT timeBeginPeriod(UINT uPeriod); 

与从所允许的范围派生的uPeriod的值

 MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc ); 

填充结构

 typedef struct { UINT wPeriodMin; UINT wPeriodMax; } TIMECAPS; 

wPeriodMin的典型值为1毫秒,wPeriodMax的典型值为1,000,000毫秒。

在这里查看最小值/最大值时有一个不幸的误解:

  • wPeriodMin定义了最小时间段,在这个上下文中是清楚的。
  • 另一方面,NtQueryTimerResolution返回的NtQueryTimerResolution指定一个分辨率。 可获得的最低分辨率(最小分辨率)在大约20毫秒的范围内,而最高可获得的分辨率(最大分辨率)可以是0.5毫秒。 但是,0.5ms的parsing不能通过timeBeginPeriod访问。

多媒体计时器接口处理周期,NtQueryTimerResolution()处理分辨率(周期的倒数值)。

总结: GetSystemTimeAdjustment不是看看的函数。 这个function只是告诉你如何以及如何改变时间。 根据多媒体计时器接口的设置timeBeginPeriod ,时间的进度可能会更频繁,更小的部分。 使用NtQueryTimerResolution来接收实际的时间增量。 而且请注意,多媒体计时器API的设置确实会影响这些值。 (例如:媒体播放器播放video时,时间越来越短。)

我在很大程度上诊断Windows时间问题。 有些结果可以在这里find。

注意: 时间调整:0.0156001清楚地标识Windows VISTA或更高与HPET和/或constant/invariant TSC在您的系统。

实现:如果你想赶上时间的过渡:

 FILETIME FileTime,LastFileTime; long long DueTime,LastTime; long FileTimeTransitionPeriod; GetSystemTimeAsFileTime(&FileTime); for (int i = 0; i < 20; i++) { LastFileTime.dwLowDateTime = FileTime.dwLowDateTime; while (FileTime.dwLowDateTime == LastFileTime.dwLowDateTime) GetSystemTimeAsFileTime(&FileTime); // enough to just look at the low part to catch the transition CopyMemory(&DueTime,&FileTime,sizeof(FILETIME)); CopyMemory(&LastTime,&LastFileTime,sizeof(FILETIME)); FileTimeTransitionPeriod = (long)(DueTime-LastTime); fprintf(stdout,"transition period: % 7.4lf ms)\n",(double)(FileTimeTransitionPeriod)/10000); } // WARNING: This code consumes 100% of the cpu for 20 file time increments. // At the standard file time increment of 15.625 ms this corresponds to 312.5ms! 

但是:当文件时间转换非常短(例如由timeBeginPeriod(wPeriodMin) )时,像fprintfstd::cout这样的输出可能会破坏结果,因为它会延迟循环。 在这种情况下,我build议将20个结果存储在数据结构中,然后再进行输出。

而且:文件时间转换可能并不总是相同的。 文件时间增量很可能与更新周期不匹配。 看到上面的链接,以获得更多的细节和这个行为的例子。

编辑: 调用timeBeginPeriod时要小心,因为频繁的调用会显着影响系统时钟 MSDN 。 此行为适用于Windows 7版本。

调用timeBeginPeriod / timeEndPeriodNtSetTimerResolution可能会将系统时间更改为ActualResolution 。 经常这样做会导致系统时间的相当大的变化。 但是,如果在系统时间或过渡时间附近进行呼叫,则偏差会小得多。 为要求苛刻的应用程序,如NTP客户端,build议在调用上述函数之前轮询系统时间转换/增量。 在系统时间进程中发生不必要的跳转时,难以同步到NTP服务器。

你实际上正在分析一个通过for()循环需要多长时间。 我得到更多的变化,但5毫秒是正确的,控制台输出不是很快。 任意添加一些更多的std :: cout语句来减慢速度。

GetSystemTimeAsFileTime的分辨率取决于系统。 如果看到它声称它在10ms和55ms之间。 MSDN文件的评论员把它放在15ms和“亚毫秒”。 它实际上是什么似乎不清楚,但我从来没有看到它的分辨率声称等于时间戳的100纳秒精度。

这意味着总是会有一些差异,而且也是人们使用QueryPerformanceFrequency的原因。