Windows 7计时功能 - 如何正确使用GetSystemTimeAdjustment?

fdl*_*dlm 10 c++ windows timing windows-7-x64

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

从此我可以看出,如果我查询系统时间,例如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;

}
Run Code Online (Sandbox Code Playgroud)

它在循环中需要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
Run Code Online (Sandbox Code Playgroud)

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

问题二:这是什么原因?

Arn*_*rno 26

GetSystemTimeAsFileTimeAPI可以访问系统的挂钟文件时间格式.

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);
Run Code Online (Sandbox Code Playgroud)

NtQueryTimerResolution由本机Windows NT库NTDLL.DLL导出.此调用报告的ActualResolution表示系统时间的更新周期,以100 ns为单位,不一定与中断周期匹配.该值取决于硬件平台.常见的硬件平台为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);
Run Code Online (Sandbox Code Playgroud)

或通过多媒体计时器界面

MMRESULT timeBeginPeriod(UINT uPeriod);
Run Code Online (Sandbox Code Playgroud)

uPeriod的值来自允许的范围

MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc );
Run Code Online (Sandbox Code Playgroud)

这填补了结构

typedef struct {
  UINT wPeriodMin;
  UINT wPeriodMax;
} TIMECAPS;
Run Code Online (Sandbox Code Playgroud)

wPeriodMin的典型值为1 ms,wPeriodMax的典型值为1,000,000 ms.

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

  • wPeriodMin定义了最小周期,在此上下文中很清楚.
  • 另一方面,返回的MinimumResolutionNtQueryTimerResolution指定分辨率.可获得的最低分辨率(MinimumResolution)在高达约20ms的范围内,而可获得的最高分辨率(MaximumResolution)可以是0.5ms.然而,0.5毫秒的结果是无法访问的timeBeginPeriod.

多媒体计时器接口处理句点,NtQueryTimerResolution()处理分辨率(句点的倒数值).

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

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

注意:时间调整: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!
Run Code Online (Sandbox Code Playgroud)

但是:当文件时间转换非常短(例如设置timeBeginPeriod(wPeriodMin))时,任何输出类似fprintfstd::cout可能会破坏结果,因为它会延迟循环.在这种情况下,我建议将20个结果存储在数据结构中,然后进行输出.

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

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

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