目录

第1章计时    1

1.1 GetTickCount    1

1.2 timeGetTime    1

1.3 QueryPerformanceCounter    1

1.4 测试    2

第2章等待    4

2.1 Sleep    4

2.2 SetWaitableTimer    4

2.3 timeSetEvent    4

2.4 轮询    5

2.5 测试    6

第3章定时器    8

3.1 SetTimer    8

3.2 SetWaitableTimer    8

3.3 timeSetEvent    9

3.4 总结    9

第1章计时

计时就是获得两个时刻之间的时间。

1.1 GetTickCount

GetTickCount是很常用的函数。它获得Windows启动时刻到当前时刻的时间,单位为毫秒。关于它有两点需要说明:

1、它的实际精度只有15ms左右,具体请参考下文介绍的测试代码;

2、GetTickCount返回的是一个32位的无符号整数,Windows连续运行49.710天后,它将再次从零开始计时。

可使用GetTickCount64代替GetTickCount,它将返回一个64位的无符号整数。Windows连续运行5.8亿年后,其计时才会归零。

1.2 timeGetTime

timeGetTime的参数、返回值、作用与GetTickCount完全一致。只是它的精度比GetTickCount要高:大部分情况下能精确到1ms,有时它也只能精确到15ms。具体请参考下文介绍的测试代码。

1.3 QueryPerformanceCounter

Windows上可以使用高性能计时器,熟悉两个 API 函数即可。

QueryPerformanceCounter与GetTickCount类似,也是获得Windows启动时刻到当前时刻的时间,不过它的单位不是毫秒。它的单位需要通过QueryPerformanceFrequency来获得。QueryPerformanceFrequency将获得一个频率Freq,它表示高性能计时器1秒钟的计数次数,也就是说QueryPerformanceCounter获得的时间是一个计数值,其单位是秒。

高性能计时器的精度:在笔者的电脑上,频率Freq为3134267,一个计数的时间是秒,也就是0.319微秒或319纳秒。这也就是高性能计时器的精度。

高性能计时器的归零:QueryPerformanceCounter获得的计数是一个有符号的64位整数。频率Freq为3134267的Windows在连续运行9.3万年后,QueryPerformanceCounter获得的计数才可能归零。

需要注意:并不是所有的电脑都支持QueryPerformanceCounter。

1.4 测试

为了比较三个计时器的精度,特编制如下代码:

//使用高性能计时器实现的 GetTickCount 函数

double GetTickCountA()

{

__int64 Freq = 0;

__int64 Count = 0;

if(QueryPerformanceFrequency((LARGE_INTEGER*)&Freq)

&& Freq > 0

&& QueryPerformanceCounter((LARGE_INTEGER*)&Count))

{

//乘以1000,把秒化为毫秒

return (double)Count / (double)Freq * 1000.0;

}

return 0.0;

}

void Test()

{

timeBeginPeriod(1);        //提高timeGetTime的精度

double        a0 = GetTickCountA();

DWORD    b0 = timeGetTime();

DWORD    c0 = GetTickCount();

Sleep(5);

double        a1 = GetTickCountA();

DWORD    b1 = timeGetTime();

DWORD    c1 = GetTickCount();

timeEndPeriod(1);         //必须与timeBeginPeriod成对出现

TRACE(_T("a=%.1lf\tb=%d\tc=%d\n"),a1-a0,b1-b0,c1-c0);

}

多次运行Test函数,可以得到如下结果:

a=5.0    b=5        c=15

a=4.9    b=5        c=0

a=1.4    b=15    c=16

a=4.2    b=5        c=0

a=4.9    b=5        c=0

如果认为高性能计时器最为可靠,就可以得到如下结论:

1、GetTickCount最不靠谱,其计时精度只有15ms左右;

2、timeGetTime大部分情况下比较靠谱,能够达到1ms的精度。但存在误差较大的情况;

3、Sleep(5)并不能准确的等待5ms。大部分情况下它会等待4.0~5.0ms,极个别的情况下会等待1.4ms、13.7ms。

第2章等待

2.1 Sleep

Sleep的用法很简单,如:Sleep(5)表示等待5ms。它最大的问题在于精度只有10ms左右。

2.2 SetWaitableTimer

使用SetWaitableTimer等待一段时间的示例代码如下:

void SleepA(double dMilliseconds)

{

HANDLE hTimer = CreateWaitableTimer(NULL,TRUE,NULL);

if(hTimer)

{

__int64 nWait = -(__int64)(dMilliseconds * 10000.0);

SetWaitableTimer(hTimer,(LARGE_INTEGER*)&nWait

,0,NULL,NULL,FALSE);

WaitForSingleObject(hTimer,INFINITE);

CloseHandle(hTimer);

}

}

首先使用CreateWaitableTimer创建一个可等待定时器——hTimer,此时hTimer是无信号的。

调用SetWaitableTimer告诉系统何时设置hTimer为有信号。注意它的第二个参数nWait。nWait是一个64位的有符号整数,正数表示绝对时间,负数表示相对时间。nWait的单位是秒,即100纳秒,dMilliseconds * 10000.0就是把毫秒转换为秒,取负号表示相对时间,即调用SetWaitableTimer之后的时间。

WaitForSingleObject用来等待hTimer有信号时返回。

2.3 timeSetEvent

使用timeSetEvent等待一段时间的示例代码如下:

void SleepB(DWORD dwMilliseconds)

{

HANDLE hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

timeSetEvent(dwMilliseconds,1,(LPTIMECALLBACK)hEvent

,0,TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);

WaitForSingleObject(hEvent,INFINITE);

CloseHandle(hEvent);

}

CreateEvent创建了一个无信号的事件;

timeSetEvent告诉系统:dwMilliseconds毫秒后设置hEvent为有信号状态;timeSetEvent的第2个参数1表示精确到1毫秒;

WaitForSingleObject用来等待hEvent有信号时返回。

2.4 轮询

使用高性能计时器轮询的等待代码如下:

void SleepC(double dMilliseconds)

{

__int64 nFreq = 0; //频率

__int64 nStart = 0; //起始计数

if(QueryPerformanceCounter((LARGE_INTEGER*)&nStart)

&& QueryPerformanceFrequency((LARGE_INTEGER*)&nFreq)

&& nFreq > 0

)

{

__int64 nEnd = 0; //终止计数

double k = 1000.0 / (double)nFreq; //将计数转换为毫秒

for(;;)

{

QueryPerformanceCounter((LARGE_INTEGER*)&nEnd);

if(dMilliseconds <= (double)(nEnd - nStart) * k)

{

break;

}

}

}

}

2.5 测试

下面是测试代码

//使用高性能计时器实现的 GetTickCount 函数

double GetTickCountA()

{

__int64 Freq = 0;

__int64 Count = 0;

if(QueryPerformanceFrequency((LARGE_INTEGER*)&Freq)

&& Freq > 0

&& QueryPerformanceCounter((LARGE_INTEGER*)&Count))

{//乘以1000,把秒化为毫秒

return (double)Count / (double)Freq * 1000.0;

}

return 0.0;

}

void Test()

{

{//Sleep

double t0 = GetTickCountA();

Sleep(5);

double t1 = GetTickCountA();

TRACE(_T("Sleep=%.3lf\t"),t1-t0);

}

{//A

double t0 = GetTickCountA();

SleepA(5.678);

double t1 = GetTickCountA();

TRACE(_T("A=%.3lf\t"),t1-t0);

}

{//B

double t0 = GetTickCountA();

SleepB(5);

double t1 = GetTickCountA();

TRACE(_T("B=%.3lf\t"),t1-t0);

}

{//C

double t0 = GetTickCountA();

SleepC(5.678);

double t1 = GetTickCountA();

TRACE(_T("C=%.3lf\n"),t1-t0);

}

}

多次运行Test函数,可得到如下结果:

Sleep=3.768 A=5.770 B=49.875 C=5.679

Sleep=4.929 A=5.995 B=4.664 C=5.679

... ... ...

Sleep=4.778 A=12.760 B=9.730 C=5.679

... ... ...

Sleep=0.222 A=9.642 B=9.744 C=5.679

Sleep=5.269 A=9.639 B=9.815 C=5.679

Sleep=6.325 A=9.544 B=9.758 C=5.679

Sleep=8.554 A=9.623 B=9.749 C=5.679

Sleep=6.380 A=9.765 B=9.790 C=5.679

Sleep=7.488 A=9.579 B=9.806 C=5.679

Sleep=0.398 A=9.717 B=9.861 C=5.679

Sleep=8.791 A=9.871 B=9.860 C=5.679

Sleep=4.329 A=9.724 B=9.818 C=5.679

Sleep=5.549 A=9.783 B=9.823 C=5.678

Sleep=0.488 A=9.684 B=9.662 C=5.679

Sleep=5.488 A=9.530 B=9.807 C=5.679

Sleep=0.560 A=9.731 B=9.727 C=5.679

Sleep=7.604 A=9.631 B=9.738 C=5.679

Sleep=8.543 A=9.476 B=9.726 C=5.679

Sleep=3.548 A=9.786 B=9.879 C=5.679

Sleep=6.672 A=9.708 B=9.835 C=5.679

Sleep=1.586 A=9.545 B=9.779 C=5.679

结论:

1、最稳定、最靠谱、精度最高的是SleepC,即使用高性能计时器轮询等待。不过,它的CPU占用率最高;

2、Sleep、SleepA、SleepB都是不够稳定的。SleepA虽然能够设置到纳秒,但实际等待时间的精度连1毫秒都达不到。

第3章定时器

3.1 SetTimer

示例代码如下

VOID CALLBACK Timer(HWND hwnd,UINT uMsg

,UINT idEvent,DWORD dwTime)

{

TRACE(_T("Time=%.3lf\n"),GetTickCountA());

}

void SetTimerAPI()

{

::SetTimer(NULL,100,1,Timer);

}

调用函数SetTimerAPI,会发现SetTimer启动的定时器,最快10毫秒执行一次,有时会20毫秒执行一次。

3.2 SetWaitableTimer

示例代码如下

VOID CALLBACK TimerA(LPVOID lpArgToCompletionRoutine

,DWORD dwTimerLowValue,DWORD dwTimerHighValue)

{

TRACE(_T("TimeA=%.3lf\n"),GetTickCountA());

}

void SetTimerA()

{

HANDLE hTimer = CreateWaitableTimer(NULL,FALSE,NULL);

if(hTimer)

{

__int64 nWait = 0;

SetWaitableTimer(hTimer,(LARGE_INTEGER*)&nWait

,1,TimerA,NULL,FALSE);

for(int i = 0;i < 100;++i)

{

SleepEx(INFINITE,TRUE);

}

CloseHandle(hTimer);

}

}

说明:SetWaitableTimer后,系统会定时把TimerA函数投递到SetWaitableTimer这行代码所在线程的APC(Asynchronous Procedure Calls)队列里。SleepEx的第2个参数为TRUE,表示一旦发现APC队列里有函数,就调用此函数,并把它从APC队列里删除,最后SleepEx会返回WAIT_IO_COMPLETION。所以,这里的SleepEx函数非常关键。

调用函数SetTimerA,会发现SetWaitableTimer启动的定时器,最快10毫秒执行一次,其执行周期比SetTimer稳定。

3.3 timeSetEvent

示例代码如下

void CALLBACK TimerB(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)

{

TRACE(_T("TimeB=%.3lf\n"),GetTickCountA());

}

void SetTimerB()

{

timeSetEvent(1,1,TimerB,0

,TIME_PERIODIC | TIME_CALLBACK_FUNCTION);

}

调用函数SetTimerB,会发现timeSetEvent启动的定时器,能够达到1毫秒执行一次,其执行周期前期非常不稳定大概0.1秒执行一次,过一段时间后就非常稳定了。

3.4 总结

1、最不靠谱的是SetTimer。它的实现原理是将WM_TIMER消息寄送至消息队列。因为消息队列里还有其它消息,它的处理时间不固定也就能够理解了;

2、SetWaitableTimer通过APC队列而不是消息队列实现了定时器。解决了定时器周期不稳定的问题,但是它的定时器周期最小也只能达到10毫秒;

3、timeSetEvent通过多线程实现了定时器(TimerB会被一个多线程调用)。使得定时器周期最小可达1毫秒。它最大的问题在于:前面一段时间(5毫秒)会以非常快的频率(0.1毫秒)调用定时处理函数。

Windows高精度时间的更多相关文章

  1. Windows下获取高精度时间注意事项

    Windows下获取高精度时间注意事项 [转贴 AdamWu]   花了很长时间才得到的经验,与大家分享. 1. RDTSC - 粒度: 纳秒级 不推荐优势: 几乎是能够获得最细粒度的计数器抛弃理由: ...

  2. Windows Server时间服务器配置方法

    1 时间服务器经常会碰到客户端机器需要和服务器在时间上保持同步,否则会出现各种问题,特别是有时间相关的触发功能的时候. 为解决各设备间时间统一的问题,我们可在网络中设置一台服务器使其作为基准时间,其它 ...

  3. linux同步windows的时间

    找了很多的资料,都没有windows做时间服务,linux同步windows的时间的,最后自己找了一些软件,终于搞定了,写出来给大家共享,以免大家多走弯路 首先在http://www.meinberg ...

  4. windows做时间服务器,linux和windows时间同步

    找了很多的资料,都没有windows做时间服务,linux同步windows的时间的,最后自己找了一些软件,终于搞定了,写出来给大家共享,以免大家多走弯路 首先在http://www.meinberg ...

  5. 查看windows到期时间

        查看windows到期时间    Slmgr.vbs  -xpr

  6. Windows系统时间(FILETIME和SYSTEMTIME)

    转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8654298 欢迎关注微博:http://weibo.com/MoreWi ...

  7. Windows下获取高精度时间注意事项 [转贴 AdamWu]

    花了很长时间才得到的经验,与大家分享. 1. RDTSC - 粒度: 纳秒级 不推荐优势: 几乎是能够获得最细粒度的计数器抛弃理由: A) 定义模糊 - 曾经据说是处理器的cycle counter, ...

  8. windows获取时间的方法

    介绍       我们在衡量一个函数运行时间,或者判断一个算法的时间效率,或者在程序中我们需要一个定时器,定时执 行一个特定的操作,比如在多媒体中,比如在游戏中等,都会用到时间函数.还比如我们通过记录 ...

  9. 一个 C# 获取高精度时间类(调用API QueryP*)

    如果你觉得用 DotNet 自带的 DateTime 获取的时间精度不够,解决的方法是通过调用 QueryPerformanceFrequency 和 QueryPerformanceCounter这 ...

随机推荐

  1. shell脚本之间互相调用

    在Shell中要如何调用别的shell脚本,或别的脚本中的变量,函数呢? 方法一: . ./subscript.sh 方法二: source ./subscript.sh 注意: .两个点之间,有空格 ...

  2. map reduce filter

    三个函数比较类似,都是应用于序列的内置函数.常见的序列包括list.tuple.str.   1.map函数 map函数会根据提供的函数对指定序列做映射. map函数的定义: map(function ...

  3. SQL高级查询——50句查询(含答案)

    -一个题目涉及到的50个Sql语句 --(下面表的结构以给出,自己在数据库中建立表.并且添加相应的数据,数据要全面些. 其中Student表中,SId为学生的ID) ----------------- ...

  4. 使用JavaScript输出

    使用JavaScript输出 1.如果需要JavaScript访问html元素,我们可以通过为html元素添加id属性,然后通过JavaScript的document.getElementById(i ...

  5. mysql 常用操作指令

    (1)centos mysql数据库文件在哪? [root@localhost ~]# find / -name mysql (2)查找数据库备份工具 mysqldump [root@localhos ...

  6. sql去除某个字段中的某个字符串 replace

    update A set col1 =REPLACE ( col1 ,'测试' , '') where col1 like '%测试%' 在使用过程中如果遇到text类型的字段时会报 参数数据类型 t ...

  7. MySql使用show processlist查看正在执行的Sql语句

    今天上班例行的查看了下服务器的运行状况,发现服务器特卡,是mysqld这个进程占用CPU到了99%导致的. 比较好奇是那个程序在使用mysql导致cpu这么高的,通过show processlist命 ...

  8. Examples For When-Validate-Item trigger In Oracle Forms

    The following example finds the commission plan in the COMMPLAN table, based on the current value of ...

  9. ZOJ 3785 What day is that day?(今天是星期几?)

    Description 题目描述 It's Saturday today, what day is it after 11 + 22 + 33 + ... + NN days? 今天是星期六,11 + ...

  10. 未能加载文件或程序集“SQLDAL”或它的某一个依赖项。系统找不到指定的文件

    1. 检查是否SQLDAL.DLL这个程序集文件是否存在,是否在Debug目录下(如果你是在Debug模式下调试).或者看看是否是配置文件中的名称和实际的dll的名称不对应. 2. 你使用的是Asse ...