【问题标题】:Accurate memory access time probing with RDTSC and RDTSCP?使用 RDTSC 和 RDTSCP 进行准确的内存访问时间探测?
【发布时间】:2016-06-14 06:51:19
【问题描述】:

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

__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 上,不应在运行时分派。 谁能告诉我是否可以改进我的代码,从而以任何方式改进结果?

【问题讨论】:

  • 根据Agner Fog's microarch pdf,英特尔 Haswell 上缓存加载->使用延迟的正确数字是 L1 为 4c,L2 为 12c。衡量这一点的一个好方法(特别是对于 L1)是指针追踪。对于 L1,只需设置一个指向自身的指针,然后在循环中运行 mov (%rax), %rax。对于 L2,您需要一个不适合 L1 的大链表。
  • 相关clflush to invalidate cache line via C function有一个关于lfence+rdtsc+lfence的详细答案。

标签: caching memory assembly x86-64 timing


【解决方案1】:

要修复您的测量代码,您需要测量一个空设置作为基线以减去测量开销,这是对的。

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

这可能解释了波动。


我通常用性能计数器而不是 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 端口,您可以将第一个 RDTSC 结果的低 32 位 mov 发送到另一个寄存器。而不是处理高32。如果您假设时间戳的差异远小于 2^32,则不需要任何一个计数的高 32 位。


我了解到,使用性能计数器比使用 TSC 可以更好地测量现代 CPU 上的此类微小序列。 Agner Fog's test programs 包含用于在程序内部使用性能计数器来测量某些东西的代码。这可以让您测量核心周期,无论是 turbo 还是非 turbo,因为核心时钟周期性能计数器实际上在每个物理时钟周期计数一个。

【讨论】:

  • 更新:lfence; rdtsc 确实序列化它,并且比 CPUID 更有效。
猜你喜欢
  • 2020-05-02
  • 2023-03-09
  • 2017-09-16
  • 1970-01-01
  • 1970-01-01
  • 2018-06-22
  • 1970-01-01
  • 1970-01-01
  • 2013-11-25
相关资源
最近更新 更多