由于需要测试一些代码(ACM代码) 的耗时情况,不得不与“高精度计时” 打交到,我们常用的耗时计算精确度达到到毫秒级应该很不错了,可是对于一些需要更高精度计时的场合来说,毫秒级计时似乎成了摆设,这时需要我们将耗时计算精确到微秒级甚至更高级别,由于我使用的是WINDOWS操作系统,关于高精度计时我们就可以使用 WINAPI函数(微秒级),或者是使用 RDTSC指令(纳秒级) 来得到我们更高精度的耗时长度。
方法一(利用WINAPI函数):
这时需要使用到的函数就是 QueryPerformanceFrequency(),我们常利用它来获得 CPU的处理频率(即每秒晶体时钟滴答的次数),QueryPerformanceCounter(), 我们常用它来获得 当前计数器的值(从开机到现在为止时钟滴答的次数总计), 我们再用公式 时间=变化次数/频率 即可得到微妙级的高精度耗时。
方法二(利用RDTSC指令):
关于 RDTSC(Read Timestamp Counter)指令,用来获取CPU的时间戳计数器的值 TSC(该值自CPU启动以来每经过一个始终周期+1),得到的数值高位存放到 edx寄存器,低位存放到 eax寄存器,可以用做随机数的种子,我们常在一段指令的前后调用它,用来得到指令总的执行时间,我们再用公式 时间=变化次数/频率 即可得到纳秒级的高精度的耗时。然而在多线程多核的今天,该指令得到的时间反而未必精确,这是由于多线程使得实际执行的指令可能乱序造成的。
PS. 主频为1G的处理器,其时钟周期是纳秒级的(1秒/1000000000=1纳秒),假设CPU主频为1G,则该计数器溢出需要的时间为:
2^64/1000000000 ≈ 18446744074 秒 ≈ 213504 天 ≈ 585 年 (就算主频达到5G也需要约117年的时间)
所以我可以自信地说该计数器在你计算机有生之年是不会溢出的了,因此你更不必担心由于溢出导致的计算错误。
我将上述两种计算代码耗时的方法整理成一个时间助手类CTimerHelper,代码如下:
#pragma once
/*************************************************************************
-- CTimeHelper Designed by SEVEN --
E-mail: 304407324@qq.com
**************************************************************************/
#ifndef __CTIMEHELPER_H__
#define __CTIMEHELPER_H__
#include <windows.h>
class CTimeHelper{
public:
typedef enum TimeMethod
{
_TIME_METHOD_WINAPI_ = 1,
_TIME_METHOD_RDTSC_
};
public:
CTimeHelper(DWORD dwMethod = _TIME_METHOD_WINAPI_)
{
QueryPerformanceFrequency(&nFreq); //查询每秒时钟周期数(即频率)
SetTimeLevel(dwMethod);
d_win_Eslaps = 0;
d_rdtsc_Eslaps = 0;
}
~CTimeHelper(){}
bool SetTimeLevel(DWORD dwMethod = _TIME_METHOD_WINAPI_)
{
bool bRes = false;
switch(dwMethod)
{
case _TIME_METHOD_WINAPI_:{
dwTimeMethod = _TIME_METHOD_WINAPI_;
bRes = true;
}
break;
case _TIME_METHOD_RDTSC_:{
dwTimeMethod = _TIME_METHOD_RDTSC_;
bRes = true;
}
break;
}
return bRes;
}
void TimeStart()
{
switch(dwTimeMethod)
{
case _TIME_METHOD_WINAPI_:{
//使用WINAPI获取当前计数器的数值
QueryPerformanceCounter(&liStart); //获取开始时计数器的数值
}
break;
case _TIME_METHOD_RDTSC_:{
//使用RDTSC指令获取时间戳计数器的值
unsigned int H, L;
__asm
{
cpuid
rdtsc
mov H, edx
mov L, eax
}
nTickStart = H;
nTickStart = (nTickStart<<32) + L;
}
break;
}
}
double TimeEnd()
{
double dTime = 0;
switch(dwTimeMethod)
{
case _TIME_METHOD_WINAPI_:{
QueryPerformanceCounter(&liEnd); // 获取结束时计数器的数值
d_win_Eslaps = (double)(liEnd.QuadPart - liStart.QuadPart)/(double)nFreq.QuadPart;
dTime = d_win_Eslaps;
}
break;
case _TIME_METHOD_RDTSC_:{
unsigned int H, L;
__asm
{
cpuid
rdtsc
mov H, edx
mov L, eax
}
nTickEnd = H;
nTickEnd = (nTickEnd<<32) + L;
d_rdtsc_Eslaps = (double)(nTickEnd-nTickStart)/(double)nFreq.QuadPart/1000;
dTime = d_rdtsc_Eslaps;
}
break;
}
return dTime;
}
double GetElapse()
{
double dTime = 0;
switch(dwTimeMethod)
{
case _TIME_METHOD_WINAPI_:{
dTime = d_win_Eslaps;
}
break;
case _TIME_METHOD_RDTSC_:{
dTime = d_rdtsc_Eslaps;
}
break;
}
return dTime;
}
private:
LARGE_INTEGER nFreq;
LARGE_INTEGER liStart, liEnd;
__int64 nTickStart, nTickEnd;
double d_win_Eslaps, d_rdtsc_Eslaps;
DWORD dwTimeMethod;
};
#endif
PS. 上述代码由于编译器以及约定调用的缘故,实际执行指令将会有所变更,对于其计时精度随着代码规模地减小而减小,通常情况下我们将函数编译增加的指令耗时以及函数调用增加的耗时忽略不记,对于上述代码我们仍可做进一步的优化,以达到更高级别的精度。
关于上述代码的使用,如下例代码:
#include<iostream>
void main()
{
CTimeHelper thlp(CTimeHelper::_TIME_METHOD_RDTSC_); //使用RDTSC方式计时
thlp.TimeStart(); //开始计时
for(int i=0;i<10000;i++){
i += i%2;
i += i%3;
i += i%4;
i += i%5;
}
thlp.TimeEnd(); //结束计时
std::cout<<"eslaps="<< thlp.GetElapse()<<" s"<<std::endl; //获取消耗的时间
}
欢迎评论和转载,转载请注明文章出处,我对此表示最真诚的敬意!