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

时间:2022-07-08 21:21:35

I ran some tests using the GetSystemTimeAdjustment function on Windows 7, and got some interesting results which I cannot explain. As fas as I understand, this method should return if the system time is synchronized periodically and if it is, at which interval and with which increment it is updated (see GetSystemTimeAdjustment function on MSDN).

我在Windows 7上使用GetSystemTimeAdjustment函数进行了一些测试,得到了一些我无法解释的有趣结果。正如我所理解的fas,如果系统时间是周期性地同步的,如果它是,在哪个间隔和哪个增量更新(请参阅MSDN上的GetSystemTimeAdjustment函数),该方法应该返回。

From this I follow that if I query the system time for example using GetSystemTimeAsFileTime repeatingly I should either get no change (the system clock has not been updated), or a change which is a multiple of the increment retrieved by GetSystemTimeAdjustment. Question one: Is this assumption correct?

因此,如果我重复地使用GetSystemTimeAsFileTime查询系统时间,那么我要么不会得到任何更改(系统时钟没有更新),要么会得到由GetSystemTimeAdjustment检索到的增量的倍数的更改。问题一:这个假设正确吗?

Now consider the following testing code:

现在考虑以下测试代码:

#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;

}

It takes 20 timestamps in a loop and prints them to the console. In the end it prints the increment with which the system clock is updated. I would expect the differences between the timestamps printed in the loop to be either 0 or multiples of this increment. However, I get results like this:

它在一个循环中需要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

So it appears that the system time is updated using an interval of about 0.0025 seconds and not 0.0156 seconds as return by GetSystemTimeAdjustment.

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

Question two: What is the reason for this?

问题二:这是什么原因?

3 个解决方案

#1


24  

The GetSystemTimeAsFileTimeAPI provides access to the system's wall clock in file time format.

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

A 64-bit FILETIME structure receives the system time as FILETIME in 100ns units, which have been expired since Jan 1, 1601. The call to GetSystemTimeAsFileTime typically requires 10 ns to 15 ns.

64位的FILETIME结构以100ns单元接收系统时间作为文件时间,这些文件时间自1601年1月1日以来已经过期。调用GetSystemTimeAsFileTime通常需要10到15 ns。

In order to investigate the real accuracy of the system time provided by this API, the granularity that comes along with the time values needs to be discussed. In other words: How often is the system time updated? A first estimate is provided by the hidden API call:

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

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

NtQueryTimerResolution is exported by the native Windows NT library NTDLL.DLL. The ActualResolution reported by this call represents the update period of the system time in 100 ns units, which does not necessarily match the interrupt period. The value depends on the hardware platform. Common hardware platforms report 156,250 or 100,144 for ActualResolution; older platforms may report even larger numbers; newer systems, particulary when HPET (High Precision Event Timer) or constant/invariant TSC are supported, may return 156,001 for ActualResolution.

NtQueryTimerResolution是由本机Windows NT库NTDLL.DLL导出的。这个调用所报告的实际解析表示系统时间在100个ns单元中的更新周期,这并不一定与中断周期相匹配。价值取决于硬件平台。通用硬件平台报告的实际分辨率为156,250或100,144。旧平台可能报告更大的数字;更新的系统,特别是支持HPET(高精度事件定时器)或常量/不变TSC时,可能会返回156,001作为实际解析。

This is one of the heartbeats controlling the system. The MinimumResolution and the ActualResolution are relevant for the multimedia timer configuration.

这是控制系统的心跳之一。最小分辨率和实际分辨率与多媒体计时器配置有关。

The ActualResolution can be set by using the API call

可以使用API调用来设置实际的解析

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

or via the multimedia timer interface

或者通过多媒体定时器接口

MMRESULT timeBeginPeriod(UINT uPeriod);

with the value of uPeriod derived from the range allowed by

用uPeriod的值从允许的范围导出

MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc );

which fills the structure

填补了结构

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

Typical values are 1 ms for wPeriodMin and 1,000,000 ms for wPeriodMax.

典型值为:1 ms表示wPeriodMin, 1,000,000 ms表示wPeriodMax。

There is an unfortunate misinterpretation when looking an the min/max values here:

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

  • wPeriodMin defines the minimum period, which is clear in this context.
  • wPeriodMin定义了最小周期,这在此上下文中是明确的。
  • MinimumResolution returned by NtQueryTimerResolution on the other hand specifies a resolution. The lowest obtainable resolution (MinimumResolution) is in the range of up to about 20 ms, while the highest obtainable resolution (MaximumResolution) can be 0.5 ms. However, the 0.5 ms resulution is not accessable through a of timeBeginPeriod.
  • 另一方面,由NtQueryTimerResolution返回的最小分辨率指定了一个分辨率。最低可得分辨率(最低分辨率)在20 ms左右,最高可得分辨率(最高分辨率)可以是0.5 ms。然而,0.5 ms的结果不能通过一个时间段访问。

The multimedia timer interface handles periods and NtQueryTimerResolution() handles resolutions (reciprocal value of period).

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

Summary: GetSystemTimeAdjustment is not the function to look at. This function only tells how and if time-changes are done. Depending on the setting of the multimedia timer interface timeBeginPeriod, the progress of time may be done more often and in smaller portions. Use NtQueryTimerResolution to receive the actual time increment. And be aware that the setting of the multimedia timer API does influence the values. (Example: When the media player is showing a video, the times are getting short.)

摘要:GetSystemTimeAdjustment不是你要看的功能。这个函数只告诉如何以及是否进行时间更改。根据多媒体定时器接口时间开始周期的设置,时间的进程可以更频繁地进行,并且以更小的部分进行。使用NtQueryTimerResolution接收实际的时间增量。并且要注意,多媒体计时器API的设置确实影响了值。(例如:当媒体播放器播放视频时,时间变得越来越短。)

I diagnosed windows time matters to a large extent. Some of the results can be found here.

我认为windows时间在很大程度上很重要。有些结果可以在这里找到。

Note: Time Adjustment: 0.0156001 clearly identifies windows VISTA or higher with HPET and/or constant/invariant TSC on your system.

注意:时间调整:0.0156001清楚地显示了windows VISTA或更高版本的HPET和/或系统上的恒定/不变TSC。

Implementation: If you want to catch the time transition:

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

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!

But: When the filetime transition is very short (e.g. set by timeBeginPeriod(wPeriodMin)) any output like fprintf or std::cout might destroy the result because it delays the loop. In such cases I'd recommend to store the 20 results in a data structure and do the output afterwards.

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

And: The filetime transition may not always be the same. It may well be that the file time increment does not match the update period. See the link above to get more details and examples for this bahavior.

并且:filetime转换可能并不总是相同的。很可能文件的时间增量与更新周期不匹配。请参阅上面的链接以获得更多关于这个bahavior的细节和示例。

Edit: Use caution when calling timeBeginPeriod, as frequent calls can significantly affect the system clock MSDN. This behavior applies up to Windows version 7.

编辑:在调用timeBeginPeriod时请注意,因为频繁调用会显著影响系统时钟MSDN。这种行为适用于Windows 7版本。

Calls to timeBeginPeriod/timeEndPeriod or NtSetTimerResolution may change the system time by as much as ActualResolution. Doing it very often results in considerable changes of the system time. However, when the calls are made at or near the transition of the system time, deviations are much less. Polling for a system time transition/increment ahead of calls to the above function is advised for demanding applications like NTP clients. Synchronizing to an NTP server is difficult when unwanted jumps in the systemtime progess occurs.

调用timeBeginPeriod/timeEndPeriod或NtSetTimerResolution可以通过实际解析改变系统时间。这样做通常会导致系统时间的相当大的变化。然而,当调用发生在系统时间的转换时,偏差会小得多。对于要求较高的应用程序(如NTP客户机),建议在调用上述函数之前轮询系统时间转换/增量。当系统时间发生不需要的跳转时,很难与NTP服务器同步。

#2


2  

You are actually profiling how long one pass through the for() loop takes. I get some more variability but 5 milliseconds is about right, console output is not very fast. Arbitrarily add some more std::cout statements to slow it down.

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

#3


2  

GetSystemTimeAsFileTime's resolution is dependent on the system. If seen it claimed that its between 10ms and 55ms. Commentators on the MSDN document put it at 15ms and "sub millisecond". What it actually is seems unclear but I've never seen its resolution claimed as equal to the 100 ns precision of the timestamp.

GetSystemTimeAsFileTime的分辨率取决于系统。如果看到它声称它在10ms和55ms之间。MSDN文件上的评论人士将其设置为15ms和“亚毫秒”。它实际上是什么似乎不清楚,但我从未见过它声称的分辨率等于时间戳的100纳秒精度。

This means there's always going to be some variance and its also the reason people use QueryPerformanceFrequency instead.

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

#1


24  

The GetSystemTimeAsFileTimeAPI provides access to the system's wall clock in file time format.

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

A 64-bit FILETIME structure receives the system time as FILETIME in 100ns units, which have been expired since Jan 1, 1601. The call to GetSystemTimeAsFileTime typically requires 10 ns to 15 ns.

64位的FILETIME结构以100ns单元接收系统时间作为文件时间,这些文件时间自1601年1月1日以来已经过期。调用GetSystemTimeAsFileTime通常需要10到15 ns。

In order to investigate the real accuracy of the system time provided by this API, the granularity that comes along with the time values needs to be discussed. In other words: How often is the system time updated? A first estimate is provided by the hidden API call:

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

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

NtQueryTimerResolution is exported by the native Windows NT library NTDLL.DLL. The ActualResolution reported by this call represents the update period of the system time in 100 ns units, which does not necessarily match the interrupt period. The value depends on the hardware platform. Common hardware platforms report 156,250 or 100,144 for ActualResolution; older platforms may report even larger numbers; newer systems, particulary when HPET (High Precision Event Timer) or constant/invariant TSC are supported, may return 156,001 for ActualResolution.

NtQueryTimerResolution是由本机Windows NT库NTDLL.DLL导出的。这个调用所报告的实际解析表示系统时间在100个ns单元中的更新周期,这并不一定与中断周期相匹配。价值取决于硬件平台。通用硬件平台报告的实际分辨率为156,250或100,144。旧平台可能报告更大的数字;更新的系统,特别是支持HPET(高精度事件定时器)或常量/不变TSC时,可能会返回156,001作为实际解析。

This is one of the heartbeats controlling the system. The MinimumResolution and the ActualResolution are relevant for the multimedia timer configuration.

这是控制系统的心跳之一。最小分辨率和实际分辨率与多媒体计时器配置有关。

The ActualResolution can be set by using the API call

可以使用API调用来设置实际的解析

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

or via the multimedia timer interface

或者通过多媒体定时器接口

MMRESULT timeBeginPeriod(UINT uPeriod);

with the value of uPeriod derived from the range allowed by

用uPeriod的值从允许的范围导出

MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc );

which fills the structure

填补了结构

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

Typical values are 1 ms for wPeriodMin and 1,000,000 ms for wPeriodMax.

典型值为:1 ms表示wPeriodMin, 1,000,000 ms表示wPeriodMax。

There is an unfortunate misinterpretation when looking an the min/max values here:

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

  • wPeriodMin defines the minimum period, which is clear in this context.
  • wPeriodMin定义了最小周期,这在此上下文中是明确的。
  • MinimumResolution returned by NtQueryTimerResolution on the other hand specifies a resolution. The lowest obtainable resolution (MinimumResolution) is in the range of up to about 20 ms, while the highest obtainable resolution (MaximumResolution) can be 0.5 ms. However, the 0.5 ms resulution is not accessable through a of timeBeginPeriod.
  • 另一方面,由NtQueryTimerResolution返回的最小分辨率指定了一个分辨率。最低可得分辨率(最低分辨率)在20 ms左右,最高可得分辨率(最高分辨率)可以是0.5 ms。然而,0.5 ms的结果不能通过一个时间段访问。

The multimedia timer interface handles periods and NtQueryTimerResolution() handles resolutions (reciprocal value of period).

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

Summary: GetSystemTimeAdjustment is not the function to look at. This function only tells how and if time-changes are done. Depending on the setting of the multimedia timer interface timeBeginPeriod, the progress of time may be done more often and in smaller portions. Use NtQueryTimerResolution to receive the actual time increment. And be aware that the setting of the multimedia timer API does influence the values. (Example: When the media player is showing a video, the times are getting short.)

摘要:GetSystemTimeAdjustment不是你要看的功能。这个函数只告诉如何以及是否进行时间更改。根据多媒体定时器接口时间开始周期的设置,时间的进程可以更频繁地进行,并且以更小的部分进行。使用NtQueryTimerResolution接收实际的时间增量。并且要注意,多媒体计时器API的设置确实影响了值。(例如:当媒体播放器播放视频时,时间变得越来越短。)

I diagnosed windows time matters to a large extent. Some of the results can be found here.

我认为windows时间在很大程度上很重要。有些结果可以在这里找到。

Note: Time Adjustment: 0.0156001 clearly identifies windows VISTA or higher with HPET and/or constant/invariant TSC on your system.

注意:时间调整:0.0156001清楚地显示了windows VISTA或更高版本的HPET和/或系统上的恒定/不变TSC。

Implementation: If you want to catch the time transition:

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

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!

But: When the filetime transition is very short (e.g. set by timeBeginPeriod(wPeriodMin)) any output like fprintf or std::cout might destroy the result because it delays the loop. In such cases I'd recommend to store the 20 results in a data structure and do the output afterwards.

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

And: The filetime transition may not always be the same. It may well be that the file time increment does not match the update period. See the link above to get more details and examples for this bahavior.

并且:filetime转换可能并不总是相同的。很可能文件的时间增量与更新周期不匹配。请参阅上面的链接以获得更多关于这个bahavior的细节和示例。

Edit: Use caution when calling timeBeginPeriod, as frequent calls can significantly affect the system clock MSDN. This behavior applies up to Windows version 7.

编辑:在调用timeBeginPeriod时请注意,因为频繁调用会显著影响系统时钟MSDN。这种行为适用于Windows 7版本。

Calls to timeBeginPeriod/timeEndPeriod or NtSetTimerResolution may change the system time by as much as ActualResolution. Doing it very often results in considerable changes of the system time. However, when the calls are made at or near the transition of the system time, deviations are much less. Polling for a system time transition/increment ahead of calls to the above function is advised for demanding applications like NTP clients. Synchronizing to an NTP server is difficult when unwanted jumps in the systemtime progess occurs.

调用timeBeginPeriod/timeEndPeriod或NtSetTimerResolution可以通过实际解析改变系统时间。这样做通常会导致系统时间的相当大的变化。然而,当调用发生在系统时间的转换时,偏差会小得多。对于要求较高的应用程序(如NTP客户机),建议在调用上述函数之前轮询系统时间转换/增量。当系统时间发生不需要的跳转时,很难与NTP服务器同步。

#2


2  

You are actually profiling how long one pass through the for() loop takes. I get some more variability but 5 milliseconds is about right, console output is not very fast. Arbitrarily add some more std::cout statements to slow it down.

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

#3


2  

GetSystemTimeAsFileTime's resolution is dependent on the system. If seen it claimed that its between 10ms and 55ms. Commentators on the MSDN document put it at 15ms and "sub millisecond". What it actually is seems unclear but I've never seen its resolution claimed as equal to the 100 ns precision of the timestamp.

GetSystemTimeAsFileTime的分辨率取决于系统。如果看到它声称它在10ms和55ms之间。MSDN文件上的评论人士将其设置为15ms和“亚毫秒”。它实际上是什么似乎不清楚,但我从未见过它声称的分辨率等于时间戳的100纳秒精度。

This means there's always going to be some variance and its also the reason people use QueryPerformanceFrequency instead.

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