使用RDTSC和RDTSCP进行准确的内存访问时间探测?

时间:2016-06-14 06:51:19

标签: caching memory assembly x86-64 timing

我正在尝试准确测量对不同缓存级别的内存访问,并提出了此代码进行探测:

__asm__ __volatile__(
        "xor %%eax, %%eax   \n"
        "xor %%edi, %%edi   \n"
        "xor %%edx, %%edx   \n"
        /* time measurement */
        "lfence              \n"
        "rdtsc              \n"
        "shl $32, %%rdx        \n"
        "or %%rdx, %%rax    \n"
        "movq %%rax, %%rdi  \n"
        /* memory access */
        "movq (%%rsi), %%rbx\n"
        /* time measurement */
        "rdtscp              \n"
        "shl $32, %%rdx     \n"
        "or %%rdx, %%rax    \n"
        "movq %%rax, %%rsi  \n"
        "cpuid              \n"
        : /* output operands */
        "=S"(t2), "=D"(t1)
        : /* input operands */
        "S" (mem)
        : /* clobber description */
        "ebx", "ecx", "edx", "cc", "memory"
    );

然而,L1和L2缓存访问仅相差8个周期且结果波动很大,因此我决定检查周围代码(除了实际内存访问)对时间的影响:

    __asm__ __volatile__(
        "xor %%eax, %%eax   \n"
        "xor %%edi, %%edi   \n"
        "xor %%edx, %%edx   \n"
        /* time measurement */
        "lfence             \n"
        "rdtsc              \n"
        "shl $32, %%rdx        \n"
        "or %%rdx, %%rax    \n"
        "movq %%rax, %%rdi  \n"
        /* memory access */
        //"movq (%%rsi), %%rbx\n"
        /* time measurement */
        "rdtscp              \n"
        "shl $32, %%rdx     \n"
        "or %%rdx, %%rax    \n"
        "movq %%rax, %%rsi  \n"
        "cpuid              \n"
        : /* output operands */
        "=S"(t2), "=D"(t1)
        : /* input operands */
        "S" (mem)
        : /* clobber description */
        "ebx", "ecx", "edx", "cc", "memory"
    );

结果如下:

./cache_testing
From Memory: 42
From L3: 46
From L2: 40
From L1: 38

./cache_testing
From Memory: 40
From L3: 38
From L2: 36
From L1: 40

我知道我目前没有达到目的不同的高速缓存级别,但我想知道为什么在缺少内存访问的情况下,时间如此波动。 代码作为具有最高优先级的SCHED_FIFO运行,固定到一个CPU,运行时不应调度。 任何人都可以告诉我,我是否可以以任何方式改进我的代码,从而改善结果?

1 个答案:

答案 0 :(得分:1)

要修正测量代码,您需要将空设置作为基线进行测量,以减去测量开销。

另外请记住,TSC会计算参考周期,而不是核心时钟周期,因此要确保CPU始终以相同的速度运行。 (例如,禁用turbo并使用预热循环使CPU达到最高速度,如果不进行超频,则TSC计数应与核心周期匹配。)

这可能解释了波动。

我通常用perf计数器测量东西,而不是RDTSC。

但我认为你应该在第一个RDTSC之前使用序列化指令(如CPUID)。在第二个RDTSC之后使用CPUID可能没用。 rdstcp for the second measurement is useful,因为它表示时间戳来自加载执行后。 (手册说“执行”; IDK,如果这意味着“退休”或者只是由负载端口执行。)

所以IIRC,你最好的选择是:

 # maybe set eax to something before CPUID
 cpuid
 rdtsc
 shl  $32, %%rdx
 lea  (%%rax, %%rdx),  %%rsi

 ... code under test

 # CPUID here, too, if you can only use rdtsc instead of rdtscp
 rdtscp
 shl  $32, %%rdx
 or   %%rdx, %%rax
 sub  %%rsi, %%rax
 # time difference in RAX     

如果被测代码与shift / LEA竞争相同的ALU端口,则只能mov第一个RDTSC结果的低32位到另一个寄存器。而不是处理高32。如果假设时间戳的差异远小于2 ^ 32,则不需要任何计数的高32位。

我已经读过,在现代CPU上测量像这样的微小序列可以通过性能计数器比使用TSC更好。 Agner Fog's test programs包括从程序内部使用perf计数器来测量某些内容的代码。这可以让您测量核心周期,无论是turbo还是非turbo,因为核心时钟周期性能计数器实际上每个物理时钟周期计数一次。