Windows 7计时功能 - 如何正确使用GetSystemTime调整?

时间:2011-10-07 10:05:03

标签: c++ windows timing windows-7-x64

我在Windows 7上使用GetSystemTimeAdjustment函数运行了一些测试,得到了一些我无法解释的有趣结果。正如我所理解的那样,如果系统时间定期同步,则该方法应该返回,如果是,则在哪个时间间隔以及更新它的增量(see GetSystemTimeAdjustment function on MSDN)。

由此我遵循,如果我重复使用GetSystemTimeAsFileTime查询系统时间,我应该不做任何更改(系统时钟尚未更新),或者是检索到的增量的倍数的更改按GetSystemTimeAdjustment问题一:这个假设是否正确?

现在考虑以下测试代码:

#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

因此,似乎系统时间使用大约0.0025秒的间隔更新,而不是GetSystemTimeAdjustment返回0.0156秒。

问题二:这是什么原因?

3 个答案:

答案 0 :(得分:25)

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

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

为了研究此API提供的系统时间的真实准确性,需要讨论随时间值提供的粒度。换句话说:系统时间多久更新一次?隐藏的API调用提供了第一个估计值:

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

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

这是控制系统的心跳之一。 MinimumResolution ActualResolution 与多媒体计时器配置相关。

可以使用API​​调用

设置 ActualResolution
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 ms,wPeriodMax的典型值为1,000,000 ms。

在这里查看最小/最大值时,有一个令人遗憾的误解:

  • wPeriodMin 定义了最短期限,在此背景下很清楚。
  • 另一方面,NtQueryTimerResolution返回的
  • MinimumResolution 指定了分辨率。可获得的最低分辨率( MinimumResolution )最高可达约20 ms,而可获得的最高分辨率( MaximumResolution )可为0.5 ms。但是,0.5 ms的结果无法通过timeBeginPeriod进行访问。

多媒体计时器界面处理句点,NtQueryTimerResolution()处理分辨率(句点的倒数)。

摘要: GetSystemTimeAdjustment不是要查看的功能。此函数仅说明如何以及是否完成时间更改。根据多媒体计时器接口timeBeginPeriod的设置,可以更频繁地以较小的部分进行时间的进展。使用NtQueryTimerResolution接收实际的时间增量。请注意,多媒体计时器API的设置确实会影响这些值。 (例如:媒体播放器播放视频时,时间越来越短。)

我在很大程度上诊断出窗口时间问题。可以找到一些结果here

注意:时间调整:0.0156001 通过系统上的HPET和/或constant/invariant TSC清楚地标识Windows VISTA或更高版本。

实施:如果您想赶上时间转换:

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之类的任何输出都可能会破坏结果,因为它会延迟循环。在这种情况下,我建议将20个结果存储在数据结构中,然后进行输出。

并且:文件时间转换可能并不总是相同。文件时间增量可能与更新周期不匹配。请参阅上面的链接以获取有关此行为的更多详细信息和示例。

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

致电timeBeginPeriod / timeEndPeriodNtSetTimerResolution可能会将系统时间更改为 ActualResolution 。经常这样做会导致系统时间发生相当大的变化。但是,当在系统时间转换时或接近转换时进行调用时,偏差要小得多。对于像NTP客户端这样要求苛刻的应用程序,建议在调用上述函数之前轮询系统时间转换/增量。当系统时间进程中发生意外跳转时,很难与NTP服务器同步。

答案 1 :(得分:2)

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

答案 2 :(得分:2)

GetSystemTimeAsFileTime的分辨率取决于系统。如果看到它声称它在10毫秒到55毫秒之间。 MSDN document上的评论员把它放在15毫秒和“亚毫秒”。它究竟是什么似乎不清楚,但我从未见过它的分辨率声称等于时间戳的100 ns精度。

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