使用RDTSC指令测量C中的代码执行时间

时间:2015-11-02 04:26:48

标签: c

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

getArguments().getString("address");

4 个答案:

答案 0 :(得分:4)

使用RDTSC计时时,您应该记住一些(非显而易见的)问题:

  1. 计算的时钟频率可能无法预测。在较旧的硬件上,频率实际上可能在两个RDTSC指令之间发生变化,即使在固定的较新硬件上,也很难分辨它运行的频率。

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

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

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

答案 1 :(得分:2)

可能影响您获得的结果的问题是:

  • 在大多数现代80x86 CPU上TSC测量的是固定频率时钟而不是周期,因此同一段代码可能会有很大的不同“周期”,具体取决于电源管理,同一内核中其他逻辑CPU的负载(超线程),其他内核的负载(turbo-boost),CPU温度(热量限制)等

  • 没有什么能阻止操作系统的调度程序在第一个rdtsc();之后立即抢占你的线程,导致产生的“循环花费分配”,以包括CPU花费在执行任意数量的完全不同的进程上的时间。 / p>

  • 在某些计算机上,不同CPU上的TSC不同步;并且没有什么能阻止操作系统在第一个rdtsc();之后立即抢占你的线程,然后在完全不同的CPU(具有完全不同的TSC)上运行你的线程。在这种情况下,end - start可能是负面的(就像时间倒退一样)。

  • 没有任何东西可以阻止IRQ(来自硬件)在第一个rdtsc();之后立即中断您的代码,导致生成的“分配周期”包括操作系统处理任意数量的IRQ所花费的时间。

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

  • 一些(旧的)CPU有一个错误,rdtsc在低32位溢出时给出狡猾的结果(例如,当TSC从0x00000000FFFFFFFF变为0x0000000100000000时,你可以准确地使用rdtsc错误的时间,得到0x0000000000000000)。

  • 没有什么能阻止“无序”现代CPU重新排列执行大多数指令的顺序,包括rdtsc指令。

  • 您的测量包括测量的开销(例如,如果rdtsc需要5个周期而您的malloc()需要20个周期,那么您需要报告25个周期而不是20个周期。

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

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

注意:我不是GCC内联汇编的专家;但我强烈怀疑你的宏是错误的,编译器可以选择生成这样的东西:

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

应该可以告诉GCC在EDX:EAX中返回值/ s并完全删除两个mov指令。

答案 2 :(得分:1)

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

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

bogomips    :  5306.71
flags       :  blah blah2 constant_tsc
processor   :  blah

如果您读取此文件,bogomips是系统引导期间计算的Mhz [sort of]的总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频率。做上面几千次。采用最小增量 - 这可以防止操作系统时间切掉你的测量结果。

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

clk2 - clk2值[已转换]为小数秒,为您提供tsc2 - tsc1的校准以及TSC刻度和秒的转换。

答案 3 :(得分:0)

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

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

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