最近在进行页热度测量方面的试验,里面需要测定一些函数、流程所花费的时间。
Linux提供的API——gettimeofday()可以获取微秒级的精度。但是,首先它不能提供纳秒级精度,其次,他是一个库函数(可能不是系统调用),自身就有一定的开销,当我需要纳秒级精度时,误差会很大。
而且,我测定函数的性能,以时钟周期为单位比纳秒更加合理。当不同型号的CPU的频率不同时,运行时间可能差很多,但是时钟周期应该差不了多少(如果指令集一样的话)。
那么怎么测定时钟周期呢?x86处理器为我们提供了rdtsc指令。从pentium开始,很多x86处理器都引入了TSC(Time Stamp Counter),一个64位的寄存器,每个CPU时钟周期其值加一。它记录了CPU从上电开始总共经历的时钟周期数。一个2.5GHz的CPU如果全速运行的话,那么其TSC就会在一秒内增加2,500,000,000。在使用过程中,不用担心TSC会溢出,因此64位整数可以“应付”一个2.5GHz的CPU运行217年!
rdtsc指令把TSC的低32位存放在EAX寄存器中,把TSC的高32位存放在EDX寄存器中。该指令可以在用户态执行。可以使用GCC内嵌汇编实现在用户态获取时钟周期:
uint64_t current_cycles() { uint32_t low, high; asm volatile("rdtsc" : "=a"(low), "=d"(high)); return ((uint64_t)low) | ((uint64_t)high << 32); }
测量时的方式就是:
uint64_t start, end; start = current_cycles(); // do something end = current_cycles();
但是该函数自身肯定也会带来开销,end - start的值不仅是“do something”的开销,也含有一次current_cycles()调用的开销。那么该函数自身的开销是多少呢?
经过测定,在不同CPU上开销还有较大差异:
Intel(R) Xeon(R) Platinum 8180M CPU @ 2.50GHz | 20 |
Intel(R) Xeon(R) Gold 5117 CPU @ 2.00GHz | 50 |
Intel(R) Xeon(R) CPU E5-2620 v4 @ 2.10GHz | 63 |
Intel(R) Core(TM) i5-4250U CPU @ 1.30Ghz | 66 |
当然,rdtsc也有很多坑,最大的问题还是在多核环境下,不同的CPU core的TSC值很可能是不同的。因此该方法仅能用于Debug环境!详见《Pitfalls of TSC usage》。