精华内容
下载资源
问答
  • Windows系统精度计时器 原理:联合使用QueryPerformanceFrequency和QueryPerformanceCounter获取高精度时间; 平台:vc++6/vc++2008
  • Windows时间精度

    千次阅读 2014-07-25 17:17:59
    Windows时间精度 Windows为非实时系统,它的Timer通常精度不是很高,下面是常用的Timer设置处理 常用的时间精度 1. SetTimer/OnTimer 根据MSDN中的定义,可以知道,最下值是参考宏USER_TIMER_MINIMUM. If uElapse...

    Windows下时间精度

    Windows为非实时系统,它的Timer通常精度不是很高,下面是常用的Timer设置处理

    常用的时间精度

    1.       SetTimer/OnTimer

    根据MSDN中的定义,可以知道,最下值是参考宏USER_TIMER_MINIMUM.

    If uElapse is less than USER_TIMER_MINIMUM, the timeout is set to USER_TIMER_MINIMUM.

    If uElapse is greater than USER_TIMER_MAXIMUM, the timeout is set to USER_TIMER_MAXIMUM.

    Windows 9855毫秒,Windows NT中,定时器的分辨率为10毫秒。

    #define USER_TIMER_MAXIMUM  0x7FFFFFFF
    #define USER_TIMER_MINIMUM  0x0000000A

     

     

    2.       Sleep(0)/Sleep(1)

    Sleep(n)的作用是让当前线程睡眠n毫秒,以便执行其他线程,如果没有其他线程,那睡眠n毫秒后,继续执行。

    根据MSDN中的定义设置为0时,检查一下是否busy,不忙时立即返回处理

    A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If no other threads of equal priority are ready to run, the function returns immediately, and the thread continues execution.

    http://blog.sina.com.cn/s/blog_605f5b4f0100zcqx.html换上了双核cpu,问题出来了:Sleep(0)经常会比预期中更早返回。)

     

    3.       timeSetEvent-timeBeginPeriod-timeEndPeriod

    调用之后用timeSetTime()注册一个回调函数,即一个中断处理过程。

    Windows驱动程序最精确的周期性通知是由Windows的多媒体服务timeSetEvent()提供的。它的时间可以精确到1毫秒。

    多媒体播放/录制控制时,可以采用这个作为精度,相对比较可靠。

     

     

    4.       QueryPerformanceFrequency() QueryPerformanceCounter()函数

    理论上可以达到更高的精度

    QueryPerformanceFrequency返回硬件支持的高精度计数器的频率。

    QueryPerformanceCounter用于得到高精度计时器的值(如果存在这样的计时器)

     

    Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu  转载请标明来源 

     

    展开全文
  • SetCpuTimeSlice,自制工具,可以设置Windows下CPU精度1~16ms,用于提高CPU的调度频率。
  • 针对windows系统的高精度定时器

    千次阅读 2017-05-21 13:09:11
    然而,这种需求虽然存在,但是由于windows系统并不是一个实时操作系统,实现这种精度的定时器,并不是一件容易的事情。 Windows 不是实时操作系统,所以任何方案都无法绝对保证定时器的精度,只是能尽量减少误差。...

    背景

      在实际工作过程中,比如与硬件交互、媒体视频播放、性能分析以及多线程任务同步时,可能需要在windows平台下实现ms级别精度的定时器。然而,这种需求虽然存在,但是由于windows系统并不是一个实时操作系统,实现这种精度的定时器,并不是一件容易的事情。

    Windows 不是实时操作系统,所以任何方案都无法绝对保证定时器的精度,只是能尽量减少误差。所以,系统的稳定性不能完全依赖于定时器,必须考虑失去同步时的处理。

    等待策略

      要想实现高精度定时器,必须需要等待和计时两种基础功能,等待用来跳过一定时间间隔,计时可以进行时间检查,用以调整等待事件。
    等待策略实际就是两种:
    1. 自旋等待:让CPU空转消耗时间,占用大量CPU时间,但是时间高度可控。
    2. 阻塞等待:线程进入阻塞状态,让出CPU时间片,在等待一定时间后再由操作系统调度回到运行状态。阻塞时不占用CPU,然而需要操作系统调度,时间难以控制。
      可以看到二者各有优劣,应该按照不同需求进行不同的实现。
    所以难点在于等待策略,下面先分析简单的自旋等待。

    自旋等待

    伪代码如下:

    
    等待开始时间=当前计时
    while((当前计时-等待开始时间)<需要等待的时间)    
    {
        自旋;
    }

    阻塞等待

      阻塞等待会把控制权交给操作系统,这样就必须确保操作系统能够及时的将定时器线程调度回运行状态。默认情况下,Windows的系统定时器精度为15.625ms,也就是说时间切片是这个尺寸。如果线程阻塞,出让其时间片进行等待,再被调度运行的时间至少是一个切片15.625ms。那么必须减少时间切片的长度,才能实现更高的精度。
     一般实现阻塞等待都用的是thread库的sleep函数,实测sleep函数的精度不高,估计也就ms级的,也就是可能会产生+1ms的误差。
     sleep会出让剩余的cpu时间片给优先级相同的线程,而yield则是出让剩余的cpu时间片给运行在同一核心上的线程。在出让的时间片结束后,其会被重新调度。一般情况下,整个过程可以在1ms内完成。

    另外,sleep或者yield在CPU高负载情况下非常不稳定,实测可能会阻塞高达几ms的时间,所以可能会产生更多的误差,因此误差修正最好采用自旋方式实现。

    定时器实现

      需要注意的是,无论自选还是阻塞,显然定时器都应该运行在独立的线程,不能干扰使用方线程工作。而对于高精度定时器而言,触发事件以执行任务的线程一般都在定时器线程内,而不是再使用独立的任务线程。
      这是因为在高精度定时场景下,任何任务的时间开销很可能大于定时器的时间间隔,如果默认就在其他线程执行任务,可能导致占用大量线程。所以应该把控制权交给用户,让用户在需要的时候自行调度任务执行的线程。

    触发模式

      由于在定时器线程执行任务,所以定时器的触发就产生了三种模式。以下是三种模式的伪代码:

    1. 固定时间框架
      比如间隔10ms,任务 7-12ms,则会按照等待 10ms 、任务 7ms、等待 3ms、任务 12ms(超时 2ms 失去同步)、任务 7ms、等待 1ms(回到同步)、任务 7ms、等待 3ms、… 进行。就是尽量按照设定好的时间框架来执行任务,只要任务不是始终超时,就可以回到原本的时间框架上。
    var 下一帧时间=0
    while(定时器开始)
    {
        下一帧时间+=间隔时间
        while(当前时间<下一帧时间)
        {
        等待;
        }
        触发任务;
    }

    2.可推迟时间框架
    上面的例子会按照等待 10ms 、任务 7ms、等待 3ms、任务 12ms(超时,推迟时间框架 2ms)、任务 7ms、等待 3ms、… 进行。超时的任务会推迟时间框架。

    var 下一帧时间 = 0;
    while(定时器开启)
    {
        下一帧时间 += 间隔时间;
        if (下一帧时间 < 当前计时)
            下一帧时间 = 当前计时
        while (当前计时 < 下一帧时间)
        {
            等待;
        }
        触发任务;
    }

    3.固定等待框架
    上面的例子会按照等待 10ms、任务 7ms、等待 10ms、任务 12ms、等待 10ms、任务 7ms… 进行。等待时间始终不变。

    while(定时器开启)
    {
        var 等待开始时间 = 当前计时;
        while ((当前计时 - 等待开始时间) < 间隔时间)
        {
            等待;
        }
        触发任务;
    }
    // 或者:
    var 下一帧时间 = 0;
    while(定时器开启)
    {
        下一帧时间 += 间隔时间;
        while (当前计时 < 下一帧时间)
        {
            等待;
        }
        触发任务;
        下一帧时间 = 当前计时;
    }

      在while循环中的等待可以使用自旋或阻塞,也可以结合它们来达到精度、稳定性和 CPU 开销的平衡。
      另外,由上面的伪代码可以看出,这三种模式的实现可以统一,能够做到根据情况切换。

    实现

      为了实现高精度的定时器,我们不采用阻塞模式(sleep函数和yield函数),而是采用QueryPerformanceFrequency()和QueryPerformanceCounter()函数。这两个函数是Visual C++提供并且仅供Windows 95及其后续版本使用,其精度与CPU的时钟频率有关,它们要求计算机从硬件上支持精确定时器。。QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下:

    BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
    BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);

    上述两个函数的参数的数据类型LARGE_INTEGER既可以是一个8字节长的整型数,也可以是两个4字节长的整型数的联合结构,其具体用法根据编译器是否支持64位而定。该类型的定义如下:

    typedef union _LARGE_INTEGER
    {
        struct
        {
            DWORD LowPart; // 4字节整型数
            LONG HighPart; // 4字节整型数
        };
        LONG QuadPart; // 8字节整型数
    }LARGE_INTEGER;

    使用QueryPerformanceFrequency()和QueryPerformanceCounter()函数进行精确定时的步骤如下:

    1. 首先调用QueryPerformanceFrequency()函数取得高精度运行计数器的频率f,单位是每秒多少次(n/s),此数一般很大;
    2. 在需要定时的代码的两端分别调用QueryPerformanceCounter()以取得高精度运行计数器的数值n1、n2,两次数值的差值通过f换算成时间间隔,t=(n2-n1)/f,当t大于或等于定时时间长度时,启动定时器;
      下面给出一个简单的计算时间的例子:
    LARGE_INTEGER m_liPerfFreq={0};
     //获取每秒多少CPU Performance Tick
     QueryPerformanceFrequency(&m_liPerfFreq); 
     LARGE_INTEGER m_liPerfStart={0};
     QueryPerformanceCounter(&m_liPerfStart);
     for(int i=0; i< 100; i++)
      cout << i << endl;
     LARGE_INTEGER liPerfNow={0};
     // 计算CPU运行到现在的时间
     QueryPerformanceCounter(&liPerfNow);
     int time=( ((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000)/m_liPerfFreq.QuadPart);
     char buffer[100];
     sprintf(buffer,"執行時間 %d millisecond ",time);
     cout<<buffer<<endl;
    

    下列再由此实现1ms的精确定时:

      LARGE_INTEGER litmp; 
           LONGLONG QPart1,QPart2;
           double dfMinus, dfFreq, dfTim; 
           QueryPerformanceFrequency(&litmp);
           dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
           QueryPerformanceCounter(&litmp);
           QPart1 = litmp.QuadPart;// 获得初始值
           do
           {
              QueryPerformanceCounter(&litmp);
              QPart2 = litmp.QuadPart;//获得中止值
              dfMinus = (double)(QPart2-QPart1);
              dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
           }while(dfTim<0.001);

      根据上述定时器,再结合之前的定时与任务的框架,就可以实现高精度的定时器,精度可以达到us级别。基本上可以满足大部分的对实时要求很高任务了。

    展开全文
  • Windows精度时间

    千次阅读 2016-12-12 11:03:54
    位的有符号整数,正数表示绝对时间,负数表示相对时间。 nWait 的单位是 秒,即 100 纳秒, dMilliseconds * 10000.0 就是把毫秒转换为 秒,取负号表示相对时间,即调用 SetWaitableTimer 之后的时间。 ...

     

    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左右,具体请参考下文介绍的测试代码;

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

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

    1.2 timeGetTime

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

    1.3 QueryPerformanceCounter

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

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

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

    高性能计时器的归零:QueryPerformanceCounter获得的计数是一个有符号的64位整数。频率Freq3134267Windows在连续运行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

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

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

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

    3Sleep(5)并不能准确的等待5ms。大部分情况下它会等待4.0~5.0ms,极个别的情况下会等待1.4ms13.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为有信号。注意它的第二个参数nWaitnWait是一个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占用率最高;

    2SleepSleepASleepB都是不够稳定的。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这行代码所在线程的APCAsynchronous 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消息寄送至消息队列。因为消息队列里还有其它消息,它的处理时间不固定也就能够理解了;

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

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

     

    展开全文
  • windows下高精度时间获取

    千次阅读 2014-04-03 19:01:35
    对关注性能的程序开发人员而言,一个好的计时部件既是益友,也是良师。计时器既可以作为程序组件帮助程序员精确的控制程序进程,又是一件有力的调试武器,在有经验的程序员手里...但这个精度对很多应用场合而言还是太粗

        对关注性能的程序开发人员而言,一个好的计时部件既是益友,也是良师。计时器既可以作为程序组件帮助程序员精确的控制程序进程,又是一件有力的调试武器,在有经验的程序员手里可以尽快的确定程序的性能瓶颈,或者对不同的算法作出有说服力的性能比较。

        在Windows平台下,常用的计时器有两种,一种是timeGetTime多媒体计时器,它可以提供毫秒级的计时。但这个精度对很多应用场合而言还是太粗糙了。另一种是QueryPerformanceCount计数器,随系统的不同可以提供微秒级的计数。对于实时图形处理、多媒体数据流处理、或者实时系统构造的程序员,善用QueryPerformanceCount/QueryPerformanceFrequency是一项基本功。


    本文要介绍的,是另一种直接利用Pentium CPU内部时间戳进行计时的高精度计时手段。以下讨论主要得益于《Windows图形编程》一书,第 15页-17页,有兴趣的读者可以直接参考该书。关于RDTSC指令的详细讨论,可以参考Intel产品手册。本文仅仅作抛砖之用。

    在 Intel Pentium以上级别的CPU中,有一个称为“时间戳(Time Stamp)”的部件,它以64位无符号整型数的格式,记录了自CPU上电以来所经过的时钟周期数。由于目前的CPU主频都非常高,因此这个部件可以达到纳秒级的计时精度。这个精确性是上述两种方法所无法比拟的。


    在Pentium以上的CPU中,提供了一条机器指令RDTSC(Read Time Stamp Counter)来读取这个时间戳的数字,并将其保存在EDX:EAX寄存器对中。由于EDX:EAX寄存器对恰好是Win32平台下C++语言保存函数返回值的寄存器,所以我们可以把这条指令看成是一个普通的函数调用。像这样:


    inline unsigned __int64 GetCycleCount()
    {
    __asm RDTSC
    }

    但是不行,因为RDTSC不被C++的内嵌汇编器直接支持,所以我们要用_emit伪指令直接嵌入该指令的机器码形式0X0F、0X31,如下:


    inline unsigned __int64 GetCycleCount()
    {
    __asm _emit 0x0F
    __asm _emit 0x31
    }

    以后在需要计数器的场合,可以像使用普通的Win32 API一样,调用两次GetCycleCount函数,比较两个返回值的差,像这样:


    unsigned long t;
    t = (unsigned long)GetCycleCount();
    //Do Something time-intensive ...
    t -= (unsigned long)GetCycleCount();

         《Windows图形编程》第15页编写了一个类,把这个计数器封装起来。有兴趣的读者可以去参考那个类的代码。作者为了更精确的定时,做了一点小小的改进,把执行RDTSC指令的时间,通过连续两次调用GetCycleCount函数计算出来并保存了起来,以后每次计时结束后,都从实际得到的计数中减掉这一小段时间,以得到更准确的计时数字。但我个人觉得这一点点改进意义不大。在我的机器上实测,这条指令大概花掉了几十到100多个周期,在 Celeron 800MHz的机器上,这不过是十分之一微秒的时间。对大多数应用来说,这点时间完全可以忽略不计;而对那些确实要精确到纳秒数量级的应用来说,这个补偿也过于粗糙了。


          我从《Windows图形编程》上把这个类的源码拷贝了下来供大家看看,下面是使用RDTSC指令的CPU时钟循环秒表类:

     

    1. // Timer.h
    2. #pragma once

    3. inline unsigned __int64 GetCycleCount(void)
    4. {
    5.     _asm  _emit 0x0F
    6.     _asm  _emit 0x31
    7. }

    8. class KTimer
    9. {
    10.     unsigned __int64 m_startcycle;

    11. public:
    12.   
    13.       unsigned __int64 m_overhead;

    14.       KTimer(void)
    15.       {
    16.           m_overhead = 0;

    17.           Start();
    18.           m_overhead  = Stop();
    19.       }

    20.       void Start(void)
    21.       {
    22.           m_startcycle = GetCycleCount();
    23.       }

    24.       unsigned __int64 Stop(void)
    25.       {
    26.           return GetCycleCount()-m_startcycle-m_overhead;
    27.       }
    28. };

     

    这个方法的优点是:

    1.高精度。可以直接达到纳秒级的计时精度(在1GHz的CPU上每个时钟周期就是一纳秒),这是其他计时方法所难以企及的。

    2. 成本低。timeGetTime 函数需要链接多媒体库winmm.lib,QueryPerformance* 函数根据MSDN的说明,需要硬件的支持(虽然我还没有见过不支持的机器)和KERNEL库的支持,所以二者都只能在Windows平台下使用(关于DOS平台下的高精度计时问题,可以参考《图形程序开发人员指南》,里面有关于控制定时器8253的详细说明)。但RDTSC指令是一条CPU指令,凡是i386平台下Pentium以上的机器均支持,甚至没有平台的限制(我相信i386版本UNIX和Linux下这个方法同样适用,但没有条件试验),而且函数调用的开销是最小的。

    (这里我想说的是:照这样看,跨平台也只能说是操作系统平台,不能跨硬件平台,就是说只能用在Intel Pentium以上的机器)



    3. 具有和CPU主频直接对应的速率关系。一个计数相当于1/(CPU主频Hz数)秒,这样只要知道了CPU的主频,可以直接计算出时间。这和 QueryPerformanceCount不同,后者需要通过QueryPerformanceFrequency获取当前计数器每秒的计数次数才能换算成时间。

    这个方法的缺点是:

    1.现有的C/C++编译器多数不直接支持使用RDTSC指令,需要用直接嵌入机器码的方式编程,比较麻烦。

    2.数据抖动比较厉害。其实对任何计量手段而言,精度和稳定性永远是一对矛盾。如果用低精度的timeGetTime来计时,基本上每次计时的结果都是相同的;而RDTSC指令每次结果都不一样,经常有几百甚至上千的差距。这是这种方法高精度本身固有的矛盾。

     

    (这里数据抖动确实是一个大问题,我遇到过这样一种情况,比如测试a和b两种算法,由于数据抖动,有时a比b耗时少,有时b比a耗时少。我想过两种测试办法:

    (1)增多测试次数,比如对a和b两种算法各测试10次,看a比b耗时少的次数和b比a耗时少的次数哪个多,以此判定哪个算法效率高。

    (2)增大测试数据量,我想一增大测试数据量,算法效率的差异就会显现出来)

    关于这个方法计时的最大长度,我们可以简单的用下列公式计算:

    自CPU上电以来的秒数 = RDTSC读出的周期数 / CPU主频速率(Hz)

    64位无符号整数所能表达的最大数字是1.8×10^19,在我的Celeron 800上可以计时大约700年(书中说可以在200MHz的Pentium上计时117年,这个数字不知道是怎么得出来的,与我的计算有出入)。无论如何,我们大可不必关心溢出的问题。

    下面是几个小例子,简要比较了三种计时方法的用法与精度

     

    1. #include <stdio.h> 
    2. #include "KTimer.h" 
    3. main() 
    4. unsigned t; 
    5. KTimer timer; 
    6. timer.Start(); 
    7. Sleep(1000); 
    8. t = timer.Stop(); 
    9. printf("Lasting Time: %d/n",t); 

    10. //Timer2.cpp 使用了timeGetTime函数 
    11. //需包含<mmsys.h>,但由于Windows头文件错综复杂的关系 
    12. //简单包含<windows.h>比较偷懒:) 
    13. //编译行:CL timer2.cpp /link winmm.lib 
    14. #include <windows.h> 
    15. #include <stdio.h> 

    16. main() 
    17. DWORD t1, t2; 
    18. t1 = timeGetTime(); 
    19. Sleep(1000); 
    20. t2 = timeGetTime(); 
    21. printf("Begin Time: %u/n", t1); 
    22. printf("End Time: %u/n", t2); 
    23. printf("Lasting Time: %u/n",(t2-t1)); 

    24. //Timer3.cpp 使用了QueryPerformanceCounter函数 
    25. //编译行:CL timer3.cpp /link KERNEl32.lib 
    26. #include <windows.h> 
    27. #include <stdio.h> 

    28. main() 
    29. LARGE_INTEGER t1, t2, tc; 
    30. QueryPerformanceFrequency(&tc); 
    31. printf("Frequency: %u/n", tc.QuadPart); 
    32. QueryPerformanceCounter(&t1); 
    33. Sleep(1000); 
    34. QueryPerformanceCounter(&t2); 
    35. printf("Begin Time: %u/n", t1.QuadPart); 
    36. printf("End Time: %u/n", t2.QuadPart); 
    37. printf("Lasting Time: %u/n",( t2.QuadPart- t1.QuadPart)); 
    38. // 这里要计算时间(单位为秒),应加上这一句
    39. double dTotalTime = (double)(t2.QuadPart-t1.QuadPart) / (double)tc.QuadPart;    //秒
    40. printf("耗时: %f/n", dTotalTime);

     

     

    //以上三个示例程序都是测试1秒钟休眠所耗费的时间
    file://测/试环境:Celeron 800MHz / 256M SDRAM
    // Windows 2000 Professional SP2
    // Microsoft Visual C++ 6.0 SP5


    以下是Timer1的运行结果,使用的是高精度的RDTSC指令
    Lasting Time: 804586872

    以下是Timer2的运行结果,使用的是最粗糙的timeGetTime API
    Begin Time: 20254254
    End Time: 20255255
    Lasting Time: 1001

    以下是Timer3的运行结果,使用的是QueryPerformanceCount API
    Frequency: 3579545
    Begin Time: 3804729124
    End Time: 3808298836
    Lasting Time: 3569712

    古人说,触类旁通。从一本介绍图形编程的书上得到一个如此有用的实时处理知识,我感到非常高兴。有美不敢自专,希望大家和我一样喜欢这个轻便有效的计时器。

     

        网上有一种说法说

    double dTotalTime=(double)(t2.QuadPart-t1.QuadPart)/(double)tc.QuadPart

    可能有问题,比如说现在很多主板都有CPU频率自动调整功能,主要是节能,尤其在笔记本上,这样除下来不能保证精确性。我不确定这种说法是否准确,供大家研究

     

       上文主要摘自《使用CPU时间戳进行高精度计时》,其实除了上面提到的三种方法,还有一种常用当然没有上面准确的办法,就是使用GetTickCount函数,这种方法能够获取毫秒级的时间,具体用法如下:

     

     

    1. DWORD startTime = GetTickCount();

    2. // do something 

    3. DWORD totalTime = GetTickCount() - startTime;

     

    参考文献:

    《使用CPU时间戳进行高精度计时》     作者:zhangyan_qd

    《Windows图形编程》,(美)Feng Yuan 著

    《VC中取得毫秒级的时间》,http://www.cppblog.com/humanchao/archive/2008/04/22/43322.html

    展开全文
  • C语言获取系统时间 & 精度

    千次阅读 2016-05-24 14:46:38
    C语言获取当前系统时间的几种方式 C语言获取系统时间的几种方式 C语言中如何获取时间?精度如何? 1 使用time_t time( time_t * timer ) 精确到秒 2 使用clock_t clock() 得到的是CPU时间精确到1/CLOCKS_PER_SEC秒...
  • windows获取时间精度到ns)

    千次阅读 2017-04-11 16:04:59
    //时间计算 static int64_t LargeIntegerToInt64(const LARGE_INTEGER& i) { return ((int64_t)i.HighPart) } static int64_t HighResTimer() { LARGE_INTEGER counter; QueryPerformanceCounter(&counter); ...
  • 修改Windows系统同步网络时间的频率

    万次阅读 2017-09-05 22:11:07
    Windows系统默认的时间同步间隔是7天,如果我们需要修改同步的时间间隔(同步频率),我们可以通过修改注册表来手动修改它的自动同步间隔以提高同步次数,保证时钟的精度,windows7,Windows8 系统: 1. 在“运行...
  • Windows中的精度定时问题

    千次阅读 2014-07-19 14:21:23
     它其实是先调用SetTimer()函数建立一个定时器,然后每隔一定的时间Windows发送一个WM_TIMER的消息,操作系统捕获此消息后处理相应的事件。但是由于Windows的定时器是建立在DOS的1CH的中断基础上的,而此中断的...
  • Windows定时精度比较

    千次阅读 2015-06-17 12:21:05
    Windows定时精度比较   方式一:VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时间隔,如SetTimer(0,200,NULL)即为设置200ms的时间间隔。然后在应用程序中增加定时响应函数 OnTimer()...
  • windows操作系统之间的时间精确同步

    千次阅读 2017-07-08 08:15:19
    分布式系统windows之前的时间同步 .相当精确。
  • Windows下高精度计时方法

    千次阅读 2005-03-24 17:09:00
    QueryPerformanceFrequency定时法://取系统时间精度:varc1:int64;t1,t2:int64;r1:double;begin QueryPerformanceFrequency(c1);//第一次调用,获得时钟晶震频率 QueryPerformanceCounter(t1);//WINDOWS API ...
  • Windows实现高精度定时器的三种方法

    万次阅读 2018-08-25 14:42:57
    前段时间一个项目需要用到1/24s的高精度定时器,每秒的误差不能超过10ms,大约41.666666666毫秒的延时,普通Sleep肯定是没办法满足的了,可以用以下新的三种方法: /* // 1秒=1000毫秒(ms) // 1毫秒=1/1000秒(s)...
  • Windows时钟函数精度测试

    千次阅读 2005-01-04 20:09:00
    Windows有个多媒体时钟函数:timeGetTimeThe timeGetTime function retrieves the system time, in milliseconds. The system time is the time elapsed since Windows was started.DWORD timeGetTime(VOID); 单位为...
  • 首先,认识一下clock()和GetTickCount(): 一、clock() ...简单而言,就是该程序从启动到函数调用占用CPU的时间。这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计
  • 如果要获得真正毫秒级甚至更高精度的当前系统时间,必须跟 CPU 打交道,别无它法。
  • Windows获取当前系统时间函数总结

    千次阅读 2011-11-15 12:53:30
    由于最近测试函数的运行时间,可是发现系统提供的大多是以毫秒级的时间精度,而算法的执行(在没有选取大量数据的情况下)竟然在毫秒级内完成了。这让我花费了一段时间来查找新的Windows函数来获取当前的时间。 先...
  • 获取系统时间几种方法 和 使用CPU时间戳进行高精度计时收藏 2009-01-22 11:182883人阅读评论(0)收藏举报 1 使用time_t time( time_t * timer ) 精确到秒  计算时间差使用double difftime( time_t timer1, ...
  • 目前常见的三大操作系统:Windows系统、Linux系统 和 Mac OS操作系统。 首先,不管是Windows操作系统、Linux系统还是苹果的Mac OS操作系统,甚至包括操作系统的鼻祖UNIX操作系统,最早都是用C语言编写的。 UNIX ...
  • 但是Windows设计之初并不是以实时系统为目标的,所以Windows系统时间精度一直不高,实际最小单位是15ms左右,导致的结果就是所有Windows的时间、线程相关的操作都无法以1ms来实现精确控制。    受影响
  • 对于相对高精度的计时: 使用供WIN9X使用的高精度定时器:QueryPerformanceFrequency()和...需包含windows.h头文件。 //需要包含 头文件 #include LARGE_INTEGER time_start, time_end; LARGE_INTEGER frequ
  • windows下C语言取系统时间

    千次阅读 2011-09-28 08:49:46
    //用标准C实现获取当前系统时间的函数 一.time()函数  time(&rawtime)函数获取当前时间距1970年1月1日的秒数,以秒计数单位,存于rawtime中。 #include "time.h" void main () { time_t rawtime; ...
  • 获取系统时间几种方法 和 使用CPU时间戳进行高精度计时  1 使用time_t time( time_t * timer ) 精确到秒 计算时间差使用double difftime( time_t timer1, time_t timer0 ) 2 使用clock_t clock() 得到的是CPU...
  • Windows几种sleep精度的测试,结果基于微秒

    万次阅读 热门讨论 2019-08-15 16:57:19
    Windows几种sleep精度的测试 在Windows环境下使用Sleep的时候,经常会遇到明明Sleep了1ms,结果被唤醒时所消耗的时间大于1ms, 对于那些精确度稍微高一点的Sleep控制时序,就不起作用了,众所周知Windows的Sleep是...
  • 内容索引:VC/C++源码,系统相关,毫秒,计时 vc++ 毫秒计时,高精度时间统计,能算出两点间的毫秒时间差,以前发布过相似的实例,有兴趣的朋友比较一下有何不同。
  • Windows获取精确系统时间-微秒级

    千次阅读 2020-02-23 00:27:46
    精确获取时间QueryPerformanceFrequency() - 基本介绍类型:Win32API原型:BOOL QueryPerformanceFreq...
  • 定时器:为 Windows 实现一个连续更新,高精度时间供应器 原著:Johan Nilsson 翻译:lxhui 原文出处:MSDN Magazine March 2004(Timers...) 原代码下载: HighResolutionTimer.exe (404KB) 本篇文章假定你熟悉 ...
  • DELPHI高精度计时方法,取毫秒级时间精度(方法一):      //取毫秒级时间精度(方法一):  var  t1,t2:int64;  r1:int64;  begin  t1:=GetTickCount;//获取开始计数 WINDOWS API  ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 35,395
精华内容 14,158
关键字:

windows系统时间精度