在Windows下面这个很容易做,不用GetTickCount()。而是封装使用QueryPerformanceCounter/Frequency()即可。但是在Unix类的系统下则遇到了问题。C/C++标准库中是没有高精度计时器,POSIX也没有。检索了一下Linux下的资料,看样子要对内核做一个小patch才能够使用,很不方便(我并不希望每换一个机器都patch一下)。
于是我打算采用古老的RDTSC来达到这个目的,虽然这个在SPEED STEP等情况会有问题,但是对测试来说是足够了。
RDTSC取的值很精确,但是每秒钟它变化频率则是一个大麻烦。从CPUID中取得的主频不可靠,INTEL的手册中给出的方法很罗嗦,根据XE的值还有不同的分支,而且我怀疑这个方法也不太靠得住。因为Intel的网站上有人给出了这样的方法:即最古老的 - 小睡片刻,然后根据这段时间差的计数变化情况计算。
既然如此,我还是用这个方法好了,算是比较可靠的一种:
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <errno.h>
/* Return ms counter */
Vm_Tick_T _vm_getOSTick()
{
/* BSD4.3 get time */
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (Vm_Tick_T) (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}
#ifdef __APPLE__
#define DO_RDTSC \
__asm { push eax } \
__asm { push edx } \
__asm { rdtsc } \
__asm { mov r_eax, eax } \
__asm { mov r_edx, edx } \
__asm { pop edx } \
__asm { pop eax }
#else
#define DO_RDTSC \
__asm__ __volatile__ ("pushl %eax\n"); \
__asm__ __volatile__ ("pushl %edx\n"); \
__asm__ __volatile__ ("rdtsc\n"); \
__asm__ __volatile__ ("movl %%eax, %0\n" : "=m" (r_eax) : ); \
__asm__ __volatile__ ("movl %%edx, %0\n" : "=m" (r_edx) : ); \
__asm__ __volatile__ ("popl %edx\n"); \
__asm__ __volatile__ ("popl %eax\n");
#endif
/* Return us counter */
Vm_Freq_T _vm_getOSUsCounter()
{
#ifdef IA_RDTSC
/* Use RDTSC */
static unsigned long long timeFreq = 0;
union
{
unsigned long r_eax_edx[2];
unsigned long long timestamp;
} u_now, u_prev;
static volatile unsigned long r_eax, r_edx;
if (timeFreq == 0)
{
/* First invoking, stat for frequency */
Vm_Tick_T c_now, c_prev;
/* Wait clock changed */
c_prev = _vm_getOSTick();
while (c_prev == _vm_getOSTick());
c_prev = _vm_getOSTick();
/* RDTSC - save time stamp counter */
DO_RDTSC;
/* Wait clock changed */
u_prev.r_eax_edx[0] = r_eax;
u_prev.r_eax_edx[1] = r_edx;
/* At least 10 ticks passed */
while (c_prev + 10 > (c_now = _vm_getOSTick()));
/* RDTSC - get new time stamp counter after sleep */
DO_RDTSC;
/* Get frequency per second */
u_now.r_eax_edx[0] = r_eax;
u_now.r_eax_edx[1] = r_edx;
timeFreq = (u_now.timestamp - u_prev.timestamp) * 1000 / (c_now - c_prev);
}
/* Get current counter */
DO_RDTSC;
u_now.r_eax_edx[0] = r_eax;
u_now.r_eax_edx[1] = r_edx;
/* Convert to us, return in 32bits */
return (Vm_Freq_T) (u_now.timestamp * 1000000 / timeFreq);
#else
/* BSD4.3 get time */
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (Vm_Freq_T) tv.tv_sec * 1000000 + tv.tv_usec;
#endif
}
其中Vm_Tick_T & Vm_Freq_T都是32位数即可。
如果使用RDTSC,则在计算时间时,第一次调用会测量10ms,从而得出频率,以后则依据频率计算。
不过,让我吐血的事情在后面:
当我做实验的时候,意外的发现其实gettimeofday返回的很准,相当的精准,准确到了微妙级别(最早我在开发driver即2001年的时候,使用的系统实现这个方法很不准,误差在10ms这个级别。几年过去了,下面的平台早已有了质的飞跃...)。也就是说,我白忙活了半天,算了,留着这段代码在driver里面做纪念吧!也许将来我会需要一个ns级别的计时器,不过那时应该依赖于微妙而不是毫秒计时器来计算Frequency。
没有评论:
发表评论