4

我在 NASM 中使用 RDTSC 和 RDTSCP 来测量各种汇编语言指令的机器周期,以帮助优化。

我阅读了英特尔 Gabriele Paoloni 撰写的“如何在英特尔 IA-32 和 IA-64 指令集架构上对代码执行时间进行基准测试”(2010 年 9 月)和其他网络资源(其中大部分是 C 语言示例)。

使用下面的代码(从 C 翻译),我测试了各种指令,但 RDTSCP 在 RDX 中总是返回零,在 RAX 中总是返回 7。我首先认为 7 是周期数,但显然并非所有指令都需要 7 个周期。

rdtsc
cpuid
addsd xmm14,xmm1 ; Instruction to time
rdtscp
cpuid

这将返回 7,这并不奇怪,因为在某些架构中,adddd 是 7 个周期,其中包括延迟。前两条指令可以(根据某些)颠倒过来,先是 cpuid,然后是 rdtsc,但这在这里没有区别。

当我将指令更改为 2 周期指令时:

rdtsc
cpuid
add rcx,rdx ; Instruction to time
rdtscp
cpuid

这也会在 rax 中返回 7,在 rdx 中返回 0。

所以我的问题是:

  1. 如何访问和解释 RDX:RAX 中返回的值?

  2. 为什么 RDX 总是返回零,它应该返回什么?

更新:

如果我将代码更改为:

cpuid
rdtsc
mov [start_time],rax
addsd xmm14,xmm1 ; INSTRUCTION
rdtscp
mov [end_time],rax
cpuid
mov rax,[end_time]
mov rdx,[start_time]
sub rax,rdx

我在 rax 中得到 64,但这听起来像太多的周期。

4

1 回答 1

10

您的第一个代码(导致标题问题)有问题,因为它用EAX、EBX、ECX 和 EDX 中的结果覆盖了rdtscrdtscp结果。cpuid

使用lfence代替cpuid; 在 Intel 自永远和启用 Spectre 缓解的 AMD 上,lfence将序列化指令流,从而用rdtsc.


请记住,RDTSC 计算参考周期,而不是核心时钟周期。 获取 CPU 周期数?为此以及有关 RDTSC 的更多信息。

您没有cpuidlfence在您的测量间隔内。但是您确实rdtscp在测量间隔内拥有自己。背靠背rdtscp并不快,如果您在没有预热 CPU 的情况下运行,64 个参考周期听起来完全合理。空闲时钟速度通常比参考周期慢很多;1 个参考周期等于或接近英特尔 CPU 上的“标贴”频率,例如最大非涡轮持续频率。例如,“4GHz”Skylake CPU 上的 4008 MHz。


这不是您为单个指令计时的方式

重要的是另一条指令可以使用结果之前的延迟,而不是直到它从无序后端完全退出之前的延迟。 RDTSC 可用于对一条加载或一条存储指令所需时间的相对变化进行计时,但开销意味着您不会得到一个好的绝对时间。

不过,您可以尝试减去测量开销。例如clflush 通过 C 函数使缓存行无效。另请参阅后续内容:使用时间戳计数器和 clock_gettime 进行缓存未命中和使用时间戳计数器进行内存延迟测量


这是我通常用来分析 short block 指令的延迟或吞吐量(以及 uops 融合和未融合域)的方法。调整你如何使用它来限制像这里这样的延迟,或者如果你只想测试吞吐量。例如,使用%rep具有足够不同寄存器的块来隐藏延迟,或者pxor xmm3, xmm3在一个短块之后使用 a 打破依赖链,让乱序 exec 发挥它的魔力。(只要你在前端没有瓶颈。)

您可能想要使用 NASM 的 smartalign 包或使用 YASM,以避免 ALIGN 指令的单字节 NOP 指令的墙壁。即使在始终支持 long-NOP 的 64 位模式下,NASM 也默认为非常愚蠢的 NOP。

global _start
_start:
    mov   ecx, 1000000000
; linux static executables start with XMM0..15 already zeroed
align 32                     ; just for good measure to avoid uop-cache effects
.loop:
    ;; LOOP BODY, put whatever you want to time in here
    times 4   addsd  xmm4, xmm3

    dec   ecx
    jnz   .loop

    mov  eax, 231
    xor  edi, edi
    syscall          ; x86-64 Linux sys_exit_group(0)

使用类似这样的单线程序运行它,将其链接到静态可执行文件并使用 配置文件perf stat您可以向上箭头并在每次更改源时重新运行

asm-link(实际上,我将nasm+ld + 可选的反汇编放入了一个%if名为代码。如果你想在头脑中测试理论时向后滚动,它就在你的终端上,就在配置文件之前。)

t=testloop; nasm -felf64 -g "$t.asm" && ld "$t.o" -o "$t" &&  objdump -drwC -Mintel "$t" &&
 taskset -c 3 perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread -r4 ./"$t"

来自 i7-6700k 在 3.9GHz 的结果(当前perf有一个辅助列的单位缩放显示错误。它已在上游修复,但 Arch Linux 尚未更新。):

 Performance counter stats for './testloop' (4 runs):

          4,106.09 msec task-clock                #    1.000 CPUs utilized            ( +-  0.01% )
                17      context-switches          #    4.080 M/sec                    ( +-  5.65% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.487 M/sec                  
    16,012,778,144      cycles                    # 3900323.504 GHz                   ( +-  0.01% )
     1,001,537,894      branches                  # 243950284.862 M/sec               ( +-  0.00% )
     6,008,071,198      instructions              #    0.38  insn per cycle           ( +-  0.00% )
     5,013,366,769      uops_issued.any           # 1221134275.667 M/sec              ( +-  0.01% )
     5,013,217,655      uops_executed.thread      # 1221097955.182 M/sec              ( +-  0.01% )

          4.106283 +- 0.000536 seconds time elapsed  ( +-  0.01% )

在我的 i7-6700k (Skylake) 上,addsd有 4 个周期延迟,0.5c 吞吐量。(即每个时钟 2 个,如果延迟不是瓶颈)。请参阅https://agner.org/optimize/https://uops.info/http://instlatx64.atw.hu/

每个分支 16 个周期 = 每个链 16 个周期 4 addsd= 4 个周期延迟addsd,即使对于这个包含少量启动开销和中断开销的测试,Agner Fog 对 4 个周期的测量结果也优于 100 分之一。

选择不同的计数器进行记录。添加:u, likeinstructions:u到 perf 甚至只会计算用户空间指令,不包括在中断处理程序期间运行的任何指令。我通常不这样做,所以我可以将开销视为挂钟时间解释的一部分。但如果你这样做cycles:u可以与.instructions:u

-r4运行它 4 次并取平均值,这对于查看是否存在大量运行之间的变化而不是仅仅从 ECX 中的较高值中获得一个平均值很有用。

调整您的初始 ECX 值以使总时间约为 0.1 到 1 秒,这通常已经足够了,尤其是当您的 CPU 非常快速地加速到最大涡轮时(例如,具有硬件 P 状态和相当激进的 energy_performance_preference 的 Skylake)。或禁用涡轮增压的最大非涡轮增压。

但这计入核心时钟周期,而不是参考周期,因此无论 CPU 频率如何变化,它仍然给出相同的结果。(+- 在转换期间停止时钟会产生一些噪音。)

于 2019-02-11T04:44:14.717 回答