我在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秒。
问题二:这是什么原因?
答案 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调用
设置 ActualResolutionNTSTATUS 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。
在这里查看最小/最大值时,有一个令人遗憾的误解:
NtQueryTimerResolution
返回的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)
设置),fprintf
或std::cout
之类的任何输出都可能会破坏结果,因为它会延迟循环。在这种情况下,我建议将20个结果存储在数据结构中,然后进行输出。
并且:文件时间转换可能并不总是相同。文件时间增量可能与更新周期不匹配。请参阅上面的链接以获取有关此行为的更多详细信息和示例。
编辑: 调用timeBeginPeriod时请小心,因为频繁的调用会显着影响系统时钟 MSDN。此行为适用于Windows版本7.
致电timeBeginPeriod
/ timeEndPeriod
或NtSetTimerResolution
可能会将系统时间更改为 ActualResolution 。经常这样做会导致系统时间发生相当大的变化。但是,当在系统时间转换时或接近转换时进行调用时,偏差要小得多。对于像NTP客户端这样要求苛刻的应用程序,建议在调用上述函数之前轮询系统时间转换/增量。当系统时间进程中发生意外跳转时,很难与NTP服务器同步。
答案 1 :(得分:2)
您实际上正在分析通过for()循环所花费的时间。我得到了一些更多的可变性但是5毫秒是正确的,控制台输出不是很快。任意添加一些std :: cout语句来减慢速度。
答案 2 :(得分:2)
GetSystemTimeAsFileTime的分辨率取决于系统。如果看到它声称它在10毫秒到55毫秒之间。 MSDN document上的评论员把它放在15毫秒和“亚毫秒”。它究竟是什么似乎不清楚,但我从未见过它的分辨率声称等于时间戳的100 ns精度。
这意味着总会出现一些差异,这也是人们使用QueryPerformanceFrequency的原因。