2015-11-02 124 views
0

我编写了一个简单的程序来测量使用RDTSC指令的代码执行时间。但我不知道我的结果是否正确,并且我的代码有问题......我不知道如何验证它。使用RDTSC指令在C中测量代码执行时间

#include <stdio.h> 
#include <assert.h> 
#include <stdint.h> 
#include <stdlib.h> 

#define N (1024*4) 

unsigned cycles_low, cycles_high, cycles_low1, cycles_high1; 

static __inline__ unsigned long long rdtsc(void) 
{ 
    __asm__ __volatile__ ("RDTSC\n\t" 
      "mov %%edx, %0\n\t" 
      "mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low):: 
      "%rax", "rbx", "rcx", "rdx"); 
} 

static __inline__ unsigned long long rdtsc1(void) 
{ 
    __asm__ __volatile__ ("RDTSC\n\t" 
      "mov %%edx, %0\n\t" 
      "mov %%eax, %1\n\t": "=r" (cycles_high1), "=r" (cycles_low1):: 
      "%rax", "rbx", "rcx", "rdx"); 
} 

int main(int argc, char* argv[]) 
{ 
    uint64_t start, end; 

    rdtsc(); 
    malloc(N); 
    rdtsc1(); 

    start = (((uint64_t)cycles_high << 32) | cycles_low); 
    end = (((uint64_t)cycles_high1 << 32) | cycles_low1); 

    printf("cycles spent in allocating %d bytes of memory: %llu\n",N, end - start); 

    return 0; 
} 
+1

您需要通过添加cpuid指令或使用rdtscp将序列化添加到rdtsc。请参阅英特尔有关进行测量的最佳做法的白皮书。 http://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html。 – Imran

+0

请勿使用'RDTSC'。使用“时钟”或操作系统特定的功能。对于Linux阅读[时间(7)](http://man7.org/linux/man-pages/man7/time.7.html),然后使用[clock_gettime(2)](http://man7.org/linux /man-pages/man2/clock_gettime.2.html) –

+0

@BasileStarynkevitch感谢您的回复。我正在研究内核2.4.37,我可否知道你推荐哪个函数来测量内核中的时间?谢谢你的时间。 – HuangJie

回答

2

有使用RDTSC时,你应该记住时间有些东西在(非显而易见性)问题:

  1. 可统计可能是不可预测的时钟频率。在较旧的硬件上,频率可能实际上在两条RDTSC指令之间变化,即使在固定的较新硬件上,也很难说出它运行的频率。

  2. 由于RDTSC没有输入,CPU本身可能会将RDTSC指令重新排序,以便在您尝试测量的代码之前出现。请注意,这与编译器对代码重新排序的问题不同,您使用__volatile__避免了这一问题。为了有效地避免这种情况,你必须执行一个序列化指令,这是一个指令,它会阻止CPU在它之前移动指令。您可以使用CPUID或RDTSCP(这只是RDTSC的序列化形式)

我的建议是:只要使用任何高频计时器API您的操作系统了。在Windows上,这是QueryPerformanceCounter,在Unix上你有gettimeofday或clock_gettime。

除此之外,您的RDTSC代码有几个结构性问题。返回类型是“无符号long long”,但实际上没有返回。如果您修复了这个问题,您可以避免将结果存储在全局变量中,并且可以避免编写多个版本。

+0

它是[clock_gettime(2)](http://man7.org/linux/man-pages /man2/clock_gettime.2.html)POSIX系统(不是'clock_gettimeofday') –

+0

好,我现在就编辑它。 –

0

注意:在我写这篇文章时,我想出了一个更简单/更简洁的方法来校准TSC转换因子。所以,继续阅读...

如果你愿意,在linux下[其他一些操作系统有相似的 - 例如。 BSD实现Linux的一部分的/ proc],在/proc/cpuinfo,你会看到字段是这样的:

bogomips : 5306.71 
flags  : blah blah2 constant_tsc 
processor : blah 

如果读此文件,bogomips是以MHz的总CPU频率[排序]系统期间计算开机。如果您的机器有速度步骤,请优先选择cpu Mhz

要使用bogomips,请计算processor行的数量,并将bogomips除以它。注意去除“。”并把它当作Khz并使用整数数学。

如果你有constant_tsc,该TSC会一直在这个[最大]频率运行,并会永远变化,如果不考虑特定的核心是由于速迈放缓。

如果阅读/proc/cpuinfo让你娇气,还有另一种方法来校准/确定TSC频率。

执行以下操作:

tsc1 = rdtsc 
clk1 = clock_gettime 

// delay for a while 
for (i = 1; i < 1000000; ++i) 
    asm volatile ("" ::: "memory"); 

clk2 = clock_gettime 
tsc2 = rdtsc 

有了这些值,可以计算TSC频率。做上述几千次。采取最低三角洲 - 这防范了操作系统时间切出你的那些测量。

使用不会导致时间片的循环计数值的最大值。实际上,您可以用替换为tv_sec = 0, tv_nsec = 500000(500 us)。 nanosleep比equiv usleep好得多。事实上,如果你想要的话,你可以在nanosleep的时间为2-3秒。

clk2 - clk2值转换]到秒的小数部分,给你tsc2 - tsc1校准和从TSC蜱和秒转换到/。

可能影响你得到的结果
1

的问题是:

  • 上最先进的80x86 CPU的TSC措施固定频率的时钟,而不是周期,因此,同一段代码可以有很大的不同“循环”取决于电源管理,同一内核中的其他逻辑CPU的负载(超线程),其他内核的负载(涡轮增压),CPU温度(热节流)等。

  • 什么都不能阻止操作系统调度程序在第一个rdtsc();之后立即抢占线程,导致产生的“花费的周期分配“来包含CPU花费在执行任意数量完全不同的进程上的时间。

  • 在某些计算机上,不同CPU上的TSC未同步;没有什么能够阻止操作系统在第一个rdtsc();之后马上抢占你的线程,然后在完全不同的CPU上运行你的线程(使用完全不同的TSC)。在这种情况下,end - start可能是负值(如时间正在倒退)。

  • 没有从第一rdtsc();后立即中断导致最后的“循环花费分配”包括操作系统花费了处理任何数量的IRQ的时候你的代码防止一个IRQ(从硬件)。

  • 无法防止导致CPU进入SMM(“系统管理模式”)的SMI(“系统管理中断”)并且在第一个导致产生的“花费分配的周期”后面执行隐藏的固件代码以包括CPU花费执行固件代码的时间。

  • 一些(旧)的CPU有一个错误在那里rdtsc给出了狡猾的结果时低32位溢出(例如,当TSC从0x00000000FFFFFFFF去0x0000000100000000你可以在精确的错误的时间使用rdtsc并获得0x0000000000000000)。

  • 没有什么能够阻止“无序”的现代CPU重新排列大多数指令的执行顺序,包括您的rdtsc指令。您的测量包括测量开销(例如,如果rdtsc需要5个周期,并且您的malloc()需要20个周期,则报告25个周期而不是20个周期)。

  • 有或没​​有虚拟机;有可能rdtsc指令是虚拟化的(例如,除了常识之外,其他任何东西都不能阻止内核报告有多少可用磁盘空间或其他喜欢的东西)。理想情况下,rdtsc应该虚拟化,以防止上面提到的大多数问题和/或阻止定时副信道(但几乎从不)。

  • 对于极其旧的CPU(80486及更早版本),TSC和rdtsc指令不存在。


注:我不是在GCC的内嵌汇编的专家;但我强烈怀疑你的宏是越野车和编译器可以选择生成这样的事情:

rdtsc 
    mov %edx, %eax  ;Oops, trashed the low 32 bits 
    mov %eax, %ebx 

应该可以告诉GCC该值/ S在EDX返回:EAX,摆脱的mov指示完全。

0

32位平台有“= A”。这从eax和edx创建了64位结果。令人遗憾的是,在64位平台上,它仅仅意味着rax寄存器,这没有任何帮助。

相反,更好的是,您可以使用“__builtin_ia32_rdtsc()”内部函数直接返回一个64位无符号整数。对于rdtscp也是如此(它也返回当前内核)。请参阅gcc手册。与使用inline asm手动执行代码相比,这些代码发出的代码稍好,并且可以在32位和64位之间移植。

如果在/ proc/cpuinfo标志中设置了“constant_tsc”,则无论CPU频率如何缩放,TSC都以恒定速率运行。如果设置了“nonstop_tsc”,则TSC继续以C(睡眠)状态运行。如果两者都设置好了,计数器“应该”也是跨核心同步的(至少在最近的CPU,Core i7或更高版本上)。我对最后一点不太确定,也许有人能纠正我?