精确定时的用途基本上分为两种:延时;定时.
操作系统任务切换有时间片的概念,即每到达一个时间片 系统就可以调度一次,来完成任务切换及多个线程的分时复用;
该时间片也就是操作系统运行时的时间分辨率:该值并不固定,可以通过
timeGetDevCaps
来获取,得到的时间片是毫秒(ms)级别.
可以修改时间片来提高Thread.Sleep()及定时器的精度.修改方法见MSDN
Timer Resolution
.
实际上可以通过一些Windows没有公开的API将时间片提高到微秒(us)级别,类似于timeGetDevCaps的对应API为
NtQueryTimerResolution
,修改时间片的对应API为
NtSetTimerResolution
.
详细描述见
Microsecond Resolution Time Services for Windows
.
使用方法如下:
1 typedef NTSTATUS (CALLBACK* NTSETTIMERRESOLUTION)
3 IN ULONG DesiredTime,
4 IN BOOLEAN SetResolution,
5 OUT PULONG ActualTime
6 );
7 NTSETTIMERRESOLUTION NtSetTimerResolution;
9 typedef NTSTATUS (CALLBACK* NTQUERYTIMERRESOLUTION)
10 (
11 OUT PULONG MaximumTime,
12 OUT PULONG MinimumTime,
13 OUT PULONG CurrentTime
14 );
15 NTQUERYTIMERRESOLUTION NtQueryTimerResolution;
17 void QueryTimerResolution(void){
19 HMODULE hNtDll = LoadLibrary(TEXT("NtDll.dll"));
20 if (hNtDll)
21 {
22 NtQueryTimerResolution = (NTQUERYTIMERRESOLUTION)GetProcAddress(hNtDll,"NtQueryTimerResolution");
23 NtSetTimerResolution = (NTSETTIMERRESOLUTION)GetProcAddress(hNtDll,"NtSetTimerResolution");
24 FreeLibrary(hNtDll);
25 }
26 if (NtQueryTimerResolution == NULL || NtSetTimerResolution == NULL){
27 printf("Search function failed!\n");
28 return ;
29 }
31 NTSTATUS nStatus;
33 ULONG Min=0;
34 ULONG Max=0;
35 ULONG Cur=0;
36 nStatus = NtQueryTimerResolution(&Max, &Min,&Cur);
38 printf("NtQueryTimerResolution -> \nMax=%lu(100ns) Min=%lu(100ns) Cur=%lu(100ns)\n",Min,Max,Cur);
40 //BOOL bSetResolution = TRUE;
41 //ULONG nActualTime;
42 //ULONG nDesiredTime = 20064;
43 //nStatus = NtSetTimerResolution (nDesiredTime, bSetResolution,&nActualTime);
有个小软件(Timer Resolution)可以获取并设置为最大时间分辨率.
对应于精确延时可以采用QueryPerformanceCounter和QueryPerformanceFrequency;
前一个函数用来获取性能计数器值,后一个函数来获取性能计数器的频率.将所需延时转换成对应的性能计数器差值,然后不断查询,等到延时时间到达.这也是Windows操作系统所能达到的“最精确的延时".
当然,也可以通过其他途径来比较时间获取完成延时功能:
GetTickCount
GetTickCount64
GetSystemTime
GetSystemTimeAsFileTime
在文章Implement a Continuously Updating, High-Resolution Time Provider for Windows 中对可以获取当前毫秒(ms)及的时间函数做了比较,由于函数底层实现的不同,采用QueryPerformanceCounter函数运行时间较长.GetSystemTimeAsFileTime是其中执行时间最短的函数.但是并没有比较GetTickCount.
在文章APIs you never heard of - the Timer APIs的评论中,Larry Osterman写到:
Centaur, QPC is heavyweight, and is documented as such. timeGetTime is faster but much less accurate.
I actually checked the code for timeGetTime and GetTickCount(). GetTickCount() takes the number of clock interrupts and multiplies it by the clock frequency. timeGetTime() reads a field called the "interrupt time", which is updated periodically by the kernel (I wasn't able to find out how frequently).
对于精确定时可选的方式有(WinAPI):
SetTimer
CreateTimerQueueTimer
timeSetEvent(MSDN建议使用CreateTimerQueueTimer替代)
在文章On WinAPI timers and their resolution中,作者对这三种方式都进行了对比,并以性能计数器为参考,得出结论
timeSetEvent定时最为精确,误差较小,CreateTimerQueueTimer次之,SetTimer最差.
对于多任务的Windows来讲,程序执行过程中不能保证不会发生任务切换的情况,故而,精确定时/延时都不会得到保证,就如文章Implement a Continuously Updating, High-Resolution Time Provider for Windows最后的结论.
Just don't perform anything requiring real-time predictability on the basis of time stamps in Windows NT.
当然,对于定时/延时要求并不非常苛刻的情况下,得到ms+级别的精度依然是可能的.采用前文所述调整系统时间分辨率(针对部分计算机可达到0.5ms甚至更低),误差会在一个或者数个时间分辨率.
综上所述:
如果进行定时操作,可以采用timeSetEvent或者CreateTimerQueueTimer,后者相对更为灵活.
如果进行延时操作,试图延时在ms一下,采用QueryPerformanceCounter的方式;在ms级别可以采用timeGetTime和GetSystemTimeAsFileTime,抑或GetTickCount.或者说简简单单使用Thread.Sleep(),针对MSDN所述其典型误差在20-60ms,这是对于XP且系统时间分辨率过大来讲的.
如果试图在Windows上得到更为精确的延时/定时,尝试使用Timer Resolution或者前文所述的方法增大系统时间分辨率,但是系统任务调度等都要付出性能代价,增大系统时间分辨率可能会导致性能方面的损耗.这方面也值得考量.
Microsecond Resolution Time Services for Windows.
Implement a Continuously Updating, High-Resolution Time Provider for Windows
APIs you never heard of - the Timer APIs
On WinAPI timers and their resolution
VC中基于 Windows 的精确定时