繁体   English   中英

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

[英]measuring code execution times in C using RDTSC instruction

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

#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;
}

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

  1. 它所计数的时钟频率可能无法预测。 在较旧的硬件上,频率实际上可能会在两个RDTSC指令之间变化,甚至在固定了该频率的较新的硬件上,也很难分辨其运行频率。

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

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

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

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

  • 在大多数现代80x86 CPU上,TSC测量的是固定频率的时钟而不是周期,因此,根据电源管理,同一内核中其他逻辑CPU的负载(超线程),同一段代码可能具有截然不同的“周期”,其他核心的负载(涡轮增压),CPU温度(热调节)等。

  • 没有什么可以阻止操作系统的调度程序在第一个rdtsc();之后立即rdtsc();您的线程rdtsc(); 导致产生的“分配周期”包括CPU执行任何数量完全不同的进程所花费的时间。

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

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

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

  • 一些(旧)CPU存在一个错误, rdtsc当低32位溢出时rdtsc会给出rdtsc结果(例如,当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中返回的,并且完全摆脱了这两个mov指令。

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

如果愿意,可以在/proc/cpuinfo的linux下[某些其他操作系统具有类似功能-例如BSD实现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频率。 做以上几千遍。 采取最小增量-这可以防止OS时间将您切分的那些测量结果。

将最大值用作不会引起时间片的循环计数。 实际上,您可以用tv_sec = 0, tv_nsec = 500000 (500 us)的nanosleep替换循环。 nanosleep比当量好得多usleep 实际上,如果您nanosleep ,您可以nanosleep 2-3秒。

clk2 - clk2值[转换为]秒分数,可为tsc2 - tsc1校准,以及与TSC滴答和秒之间的转换。

对于32位平台,存在“ = A”。 这将从eax和edx创建64位结果。 可悲的是,在64位平台上,它仅表示rax寄存器,这无济于事。

相反,更好的是,您可以使用“ __builtin_ia32_rdtsc()”内部函数来直接返回64位无符号整数。 对于rdtscp(也返回当前内核)类似。 请参阅gcc手册。 与使用内联汇编手动完成相比,它们确实发出了更好的代码,并且可以在32位和64位之间移植。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM