一种制作微秒级精度定时器的方法(转贴)
当使用定时器时,在很多情况下只用到毫秒级的时间间隔,所以只需用到下面的两种常用方式就满足要求了。一是用SetTimer函数建立一个定时器后,在程序中通过处理由定时器发送到线程消息队列中的WM_TIMER消息,而得到定时的效果(退出程序时别忘了调用和SetTimer配对使用的KillTimer函数)。二是利用GetTickCount函数可以返回自计算机启动后的时间,通过两次调用GetTickCount函数,然后控制它们的差值来取得定时效果,此方式跟第一种方式一样,精度也是毫秒级的。
用这两种方式取得的定时效果虽然在许多场合已经满足实际的要求,但由于它们的精度只有毫秒级的,而且在要求定时时间间隔小时,实际定时误差大。下面介绍一种能取得高精度定时的方法。
在一些计算机硬件系统中,包含有高精度运行计数器(high-resolution performance counter),利用它可以获得高精度定时间隔,其精度与CPU的时钟频率有关。采用这种方法的步骤如下:
1、首先调用QueryPerformanceFrequency函数取得高精度运行计数器的频率f。单位是每秒多少次(n/s),此数一般很大。
2、在需要定时的代码的两端分别调用QueryPerformanceCounter以取得高精度运行计数器的数值n1,n2。两次数值的差值通过f换算成时间间隔,t=(n2-n1)/f。
下面举一个例子来演示这种方法的使用及它的精确度。
在VC 6.0 下用MFC建立一个对话框工程,取名为HightTimer.在对话框面板中控件的布局如下图:
其中包含两个静态文本框,两个编辑框和两个按纽。上面和下面位置的编辑框的ID分别为IDC_E_TEST和IDC_E_ACTUAL,通过MFC ClassWizard添加的成员变量也分别对应为DWORD m_dwTest和DWORD m_dwAct. “退出”按纽的ID为IDOK,“开始测试”按纽ID为IDC_B_TEST,用MFC ClassWizard添加此按纽的单击消息处理函数如下:
void CHightTimerDlg::OnBTest()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE); //取输入的测试时间值到与编辑框相关联的成员变量m_dwTest中
LARGE_INTEGER frequence;
if(!QueryPerformanceFrequency( &frequence)) //取高精度运行计数器的频率,若硬件不支持则返回FALSE
MessageBox("Your computer hardware doesn't support the high-resolution performance counter",
"Not Support", MB_ICONEXCLAMATION | MB_OK);
LARGE_INTEGER test, ret;
test.QuadPart = frequence.QuadPart * m_dwTest / 1000000; //通过频率换算微秒数到对应的数量(与CPU时钟有关),1秒=1000000微秒
ret = MySleep( test ); //调用此函数开始延时,返回实际花销的数量
m_dwAct = (DWORD)(1000000 * ret.QuadPart / frequence.QuadPart ); //换算到微秒数
UpdateData(FALSE); //显示到对话框面板
}
其中上面调用的MySleep函数如下:
LARGE_INTEGER CHightTimerDlg::MySleep(LARGE_INTEGER Interval)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 功能:执行实际的延时功能
// 参数:Interval 参数为需要执行的延时与时间有关的数量
// 返回值:返回此函数执行后实际所用的时间有关的数量
///////////////////////////////////////////////////////////////////////////////////////////////////////////
{
LARGE_INTEGER privious, current, Elapse;
QueryPerformanceCounter( &privious );
current = privious;
while( current.QuadPart - privious.QuadPart < Interval.QuadPart )
QueryPerformanceCounter( ¤t );
Elapse.QuadPart = current.QuadPart - privious.QuadPart;
return Elapse;
}
注:别忘了在头文件中为此函数添加函数声明。
至此,可以编译和执行此工程了,结果如上图所示。在本人所用的机上(奔腾366, 64M内存)测试,当测试时间超过3微秒时,准确度已经非常高了,此时机器执行本身延时函数代码的时间对需要延时的时间影响很小了。
上面的函数由于演示测试的需要,没有在函数级封装,下面给出的函数基本上可以以全局函数的形式照搬到别的程序中。
BOOL MySleep(DWORD dwInterval)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 功能:执行微秒级的延时功能
// 参数:Interval 参数为需要的延时数(单位:微秒)
// 返回值:若计算机硬件不支持此功能,返回FALSE,若函数执行成功,返回TRUE
///////////////////////////////////////////////////////////////////////////////////////////////////////////
{
BOOL bNormal = TRUE;
LARGE_INTEGER frequence, privious, current, interval;
if(!QueryPerformanceFrequency( &frequence))
{
::MessageBox(NULL, "Your computer hardware doesn't support the high-resolution performance counter",
"Not Support", MB_ICONEXCLAMATION | MB_OK); //或其它的提示信息
return FALSE;
}
interval.QuadPart = frequence.QuadPart * dwInterval / 1000000;
bNormal = bNormal && QueryPerformanceCounter( &privious );
current = privious;
while( current.QuadPart - privious.QuadPart < interval.QuadPart )
bNormal = bNormal && QueryPerformanceCounter( ¤t );
return bNormal;
}
需要指出的是,由于在此函数中的代码很多,机器在执行这些代码所花费的时间也很长,所以在需要几个微秒的延时时,会影响精度。实际上,读者在熟悉这种方法后,只要使用QueryPerformanceFrequency和QueryPerformanceCounter这两个函数就能按实际需要写出自己的延时代码了。