【问题标题】:Windows 7 timing functions - How to use GetSystemTimeAdjustment correctly?Windows 7 计时功能 - 如何正确使用 GetSystemTimeAdjustment?
【发布时间】:2011-12-02 21:36:34
【问题描述】:

我在 Windows 7 上使用GetSystemTimeAdjustment 函数进行了一些测试,得到了一些我无法解释的有趣结果。据我了解,如果系统时间定期同步,则此方法应返回,如果是,则更新时间间隔和增量(see GetSystemTimeAdjustment function on MSDN)。

据此我认为,如果我查询系统时间,例如使用GetSystemTimeAsFileTime 重复,我应该没有变化(系统时钟尚未更新),或者变化是@检索到的增量的倍数987654324@。 问题一:这个假设正确吗?

现在考虑以下测试代码:

#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 秒。

问题二:这是什么原因?

【问题讨论】:

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


    【解决方案1】:

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

    64 位 FILETIME 结构接收系统时间为 FILETIME,以 100ns 为单位,自 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代表系统时间的更新周期,以100ns为单位,不一定与中断周期匹配。该值取决于硬件平台。常见的硬件平台报告 ActualResolution 为 156,250 或 100,144;较旧的平台可能会报告更大的数字;较新的系统,特别是在支持 HPET(高精度事件计时器)或 constant/invariant TSC 时,可能会为 ActualResolution 返回 156,001。

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

    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 返回的 MinimumResolution 指定了一个分辨率。可获得的最低分辨率 (MinimumResolution) 在高达约 20 毫秒的范围内,而可获得的最高分辨率 (MaximumResolution) 可以是 0.5 毫秒。但是,无法通过timeBeginPeriod 访问 0.5 ms 分辨率。

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

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

    我诊断出 Windows 时间在很大程度上很重要。部分结果可以在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 服务器同步。

    【讨论】:

    • 这里有一个小工具和 C# 代码来使用所描述的方法:github.com/tebjan/TimerTool
    • NtQueryTimerResolution 的第二个参数必须是 OUT PULONG MaximumResolution,而不是 OUT LONGMaximumResolution。还要在参数类型 (PULONG) 和参数名称之间添加空格。
    【解决方案2】:

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

    【讨论】:

    • 否,GetSystemTimeAdjustment 不返回时钟滴答中断周期。
    • NtQueryTimerResolution (NTDLL.DLL) 将返回时钟滴答。 GetSystemTimeAdjustment 只会让您知道系统时间调整是否处于活动状态以及更新增量是多少。
    • 好吧,Why is a fprintf so slow 可能会让你摆脱阻塞输出。我建议 less 输出来真正轮询文件时间转换。
    • GetSystemTimeAdjustment() 通过第二个参数返回时钟中断周期。 156001 在我的机器上,正如预期和记录的那样。这似乎是有争议的,所以我把它删除了。
    • @HansPassant:默认情况下,时间增量间隔设置为与时钟中断周期相同的值并不奇怪(这意味着每个时钟滴答都会进行一次调整)但根据文档它们不是同义词。
    【解决方案3】:

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

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

    【讨论】:

    • 我在链接的 MSDN 文档中找不到那些 cmets。但是,GetSystemTimeAsFileTime() 的粒度由GetSystemTimeAdjustment() 返回的 TimeIncrement 给出。这不应与系统文件时间表示 100ns 单位的能力混淆。
    • @Arno 我已经更新了旧文档的链接。感谢那。顺便说一句,您为什么认为 GetSystemTimeAsFileTimeGetSystemTimeAdjustment 之间存在关系
    • 系统文件时间代表 100 ns 单位。但它不会以 100 ns 的步长递增。文件时间更新量和速率由一个复杂的机制决定,该机制关注各种硬件。这包括中断周期和可能的节拍频率,如果中断周期与文件时间量 (TimeIncrement) 不匹配,则会发生这种情况。进一步阅读:我在下面的回答和this
    猜你喜欢
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    • 1970-01-01
    • 2015-07-30
    • 2010-12-09
    • 2019-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多