极客必备之C++ 定时器

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

      1.  毫秒级别的定时器

         Windows下面有很多毫秒级别的定时器,这里只介绍两个。 clock()  和 GetTickCount()

         接下来所有代码的开发环境都是 Windows + Visual Studio 2010。


         (1)GetTickCount():

 

<span style="font-size:18px;">#include "stdafx.h"
#include <windows.h>
#include <time.h>
#include <iostream>
using namespace std;

int main()
{
    DWORD begin , end ;

	// 获取从开机到本函数调用所经过的毫秒数
	begin = ::GetTickCount();

	/* 这里是需要测试时间的代码块 */
	Sleep( 2 );

	// 再次获取从开机到本函数调用所经过的毫秒数
	end   = ::GetTickCount();

	// 输出经过的时间 , 单位为毫秒
	DWORD time_ms = end - begin ;
	cout<<"经过了"<< time_ms <<" ms."<<endl;
    
    return 0;
}</span>

   测试结果:


 

    这个函数的存在 10-15ms 的计时误差。


    (2)Clock():

   

<span style="font-size:18px;">#include "stdafx.h"
#include <windows.h>
#include <time.h>
#include <iostream>
using namespace std;

int main()
{
    time_t begin , end ;

	// 获取从开机到本函数调用所经过的周期数
	begin = clock();

	Sleep( 2 );

	// 再次获取从开机到本函数调用所经过的周期数
	end   = clock();

	// 输出经过的时间 , 单位为毫秒
	int time_ms = (int) ( 1.0 * ( end - begin ) / CLOCKS_PER_SEC * 1000 );

	cout<<"经过了"<< time_ms <<" ms."<<endl;
    
    return 0;
}</span>

 测试结果:


    该函数也存在着计时误差,至于具体有多大的误差,暂时没有研究。


    2. 微秒级别的定时器

    微秒级别的定时器,最常见的就是 QueryPerformanceCounter 。

  

<span style="font-size:18px;">#include"stdafx.h"
#include<Windows.h>
#include<iostream>
using namespace std;

int main()
{
    // 说明
    // begin : 从开机到现在的滴答次数
    // end   : 再次获取从开机到现在的滴答次数
    // freq  : 计时器在 1秒时间内滴答的次数,也就是频率。
    LARGE_INTEGER begin , end , freq;

    // 获取频率
    QueryPerformanceFrequency( &freq );
    printf("Frequency: %u\n", freq.QuadPart);

    QueryPerformanceCounter( &begin );

    // 测试时间的代码块
    Sleep(1000);

    QueryPerformanceCounter( &end );

    printf("Begin Time: %u\n", begin.QuadPart);
    printf("End Time: %u\n", end.QuadPart);

    // 计算经过的微秒数
    long long time_micro_second = 1.0 * ( end.QuadPart- begin.QuadPart ) / freq.QuadPart * 1000000 ;
    cout<<"经过了"<<time_micro_second<<" 微秒"<<endl;
}</span>

   测试结果:

 

    仍然存在一定的误差,我在 MSDN 上面找到了关于误差的描述:

Resolution, Precision, Accuracy, and Stability

QPC uses a hardware counter as its basis. Hardware timers consist of three parts: a tick generator, a counter that counts the ticks, and a means of retrieving the counter value. The characteristics of these three components determine the resolution, precision, accuracy, and stability ofQPC.

If a hardware generator provides ticks at a constant rate, time intervals can be measured by simply counting these ticks. The rate at which the ticks are generated is called the frequency and expressed in Hertz (Hz). The reciprocal of the frequency is called the period or tick interval and is expressed in an appropriate International System of Units (SI) time unit (for example, second, millisecond, microsecond, or nanosecond).

     也就是说,这个方法的误差取决于 1.频率发生器 2.次数记录器 3.次数记录器的读取。


     3. 纳秒级别的定时器

           最常见的就是 GetCycleCount 。

        在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函数,比较两个返回值的差. 这个时候,计数的频率就是 CPU 的主频了,比如,我的i5处理器, 2.6GHz , 睿频可达3.2G , Freqency 就是 2.6G

       测试代码:

<span style="font-size:18px;"><span style="font-size:14px;">#include "stdafx.h"
#include<iostream>
#include<Windows.h>
using namespace std;

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

int _tmain(int argc, _TCHAR* argv[])
{
	long long start = GetCycleCount();

	Sleep(1000);

	long long end = GetCycleCount();

	// 获取经过的时钟周期数
	long long time = end - start ;


	cout<<"经过了" << time<<" 个时钟周期"<< endl;
	return 0;
}</span></span>

   测试结果:

  

    说明在 Sleep(1000) 的时间内,经过了2594297034 个时钟周期。


    接下来用这个方法测试 Sleep(1) 实际上花费的时间。

<span style="font-size:18px;"><span style="font-size:14px;">#include "stdafx.h"
#include<iostream>
#include<Windows.h>
using namespace std;

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

int _tmain(int argc, _TCHAR* argv[])
{
	// 我的 CPU 的主频是2.6 GHz ,因此就是2600000000
	long long freq = 2600000000;

	long long start = GetCycleCount();

	Sleep(1);

	long long end = GetCycleCount();

	// 获取经过的时钟周期数
	long long time = end - start ;
	// 计算耗费的毫秒数
	int time_ms = 1.0 * time / freq * 1000 ; 


	cout<<"实际上耗费了" << time_ms <<" ms"<< endl;
	return 0;
}</span></span>

测试结果:

   由此可见,这种方法相当准确。


    至此,不同精度的定时器就介绍完了~

  

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值