X86平台采用rdtsc指令读取时间戳计数器完成高精度计时
从pentium开始,很多80x86微处理器都引入TSC,一个用于时间戳计数器的64位的寄存器,它在每个时钟信号(CLK, CLK是微处理器中一条用于接收外部振荡器的时钟信号输入引线)到来时加一。
通过它可以计算CPU的主频,比如:如果微处理器的主频是1MHZ的话,那么TSC就会在1秒内增加1000000。除了计算CPU的主频外,还可以通过TSC来测试微处理器其他处理单元的运算速度,http://www.h52617221.de/dictionary.php?id=278 介绍了这个内容。
那么如何获取TSC的值呢?rdtsc,一条读取TSC的指令,它把TSC的低32位存放在eax寄存器中,把TSC的高32位存放在edx中,更详细的描述见资料[1]。
下面来看看rdtsc的具体用法,在linux源代码include/asm-i386/msr.h中,可以找到这么三个关于rdtsc的宏定义:
#define rdtsc(low,high) \
__asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))
#define rdtscl(low) \
__asm__ __volatile__("rdtsc" : "=a" (low) : : "edx")
#define rdtscll(val) \
__asm__ __volatile__("rdtsc" : "=A" (val))
如果我们想准确的知道一段程序,一个函数的执行时间,可以连续执行2次rdtsc,之间没有一行代码,来计算这两段指令执行过程会有的cycle数,不同机器可能都会有不同,和机器的性能有关系,但和负载没关系,也就是多进程,多线程情况下,连续两个rdtsc之间不会插入很多cycle。
static unsigned cyc_hi = 0; static unsigned cyc_lo = 0; /* Set *hi and *lo to the high and low order bits of the cycle counter. * Implementation requires assembly code to use the rdtsc instruction. */ void access_counter(unsigned *hi, unsigned *lo) { asm("rdtsc; movl %%edx, %0; movl %%eax, %1" : "=r" (*hi), "=r" (*lo) : /* No input */ : "%edx", "%eax"); return; } /* Record the current value of the cycle counter. */ void start_counter(void) { access_counter(&cyc_hi, &cyc_lo); return; }
RDTSC只在X86下有效,其余平台会有类似指令来做准确计数,RDTSC指令的精度是可以接受的,里面能插得cycle是很有限的。如果对计数要求没那么高,可以采用一些通用库函数,当然你也可以用类似的方法来考察这些库函数的精度,连续执行2次就行。
/* Return the number of cycles since the last call to start_counter. */ double get_counter(void) { unsigned int ncyc_hi, ncyc_lo; unsigned int hi, lo, borrow; double result; /* Get cycle counter */ access_counter(&ncyc_hi, &ncyc_lo); /* Do double precision subtraction */ lo = ncyc_lo - cyc_lo; borrow = lo > ncyc_lo; hi = ncyc_hi - cyc_hi - borrow; result = (double)hi * (1 << 30) * 4 + lo; if (result < 0) { printf("Error: counter returns neg value: %.0f\n", result); } return result; }
函数调用方式:
start_counter();
tmp = fast_log(input);
cnt = get_counter();
printf("tmp = %f. clk counter = %lf.\n",tmp,cnt);