最近想模拟个波形发生器,查阅了一下windows的精确定时能力.
精确定时的用途基本上分为两种:延时;定时.
支配性参数--Timer Resolution
操作系统任务切换有时间片的概念,即每到达一个时间片 系统就可以调度一次,来完成任务切换及多个线程的分时复用;
该时间片也就是操作系统运行时的时间分辨率:该值并不固定,可以通过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) 2 ( 3 IN ULONG DesiredTime, 4 IN BOOLEAN SetResolution, 5 OUT PULONG ActualTime 6 ); 7 NTSETTIMERRESOLUTION NtSetTimerResolution; 8 9 typedef NTSTATUS (CALLBACK* NTQUERYTIMERRESOLUTION) 10 ( 11 OUT PULONG MaximumTime, 12 OUT PULONG MinimumTime, 13 OUT PULONG CurrentTime 14 ); 15 NTQUERYTIMERRESOLUTION NtQueryTimerResolution; 16 17 void QueryTimerResolution(void){ 18 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 } 30 31 NTSTATUS nStatus; 32 33 ULONG Min=0; 34 ULONG Max=0; 35 ULONG Cur=0; 36 nStatus = NtQueryTimerResolution(&Max, &Min,&Cur); 37 38 printf("NtQueryTimerResolution -> \nMax=%lu(100ns) Min=%lu(100ns) Cur=%lu(100ns)\n",Min,Max,Cur); 39 40 //BOOL bSetResolution = TRUE; 41 //ULONG nActualTime; 42 //ULONG nDesiredTime = 20064; 43 //nStatus = NtSetTimerResolution (nDesiredTime, bSetResolution,&nActualTime); 44 }
有个小软件(Timer Resolution)可以获取并设置为最大时间分辨率.
精确延时
对应于精确延时可以采用QueryPerformanceCounter和QueryPerformanceFrequency;
前一个函数用来获取性能计数器值,后一个函数来获取性能计数器的频率.将所需延时转换成对应的性能计数器差值,然后不断查询,等到延时时间到达.这也是Windows操作系统所能达到的“最精确的延时".
当然,也可以通过其他途径来比较时间获取完成延时功能:
在文章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):
在文章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或者前文所述的方法增大系统时间分辨率,但是系统任务调度等都要付出性能代价,增大系统时间分辨率可能会导致性能方面的损耗.这方面也值得考量.
参考
来源: