代码之家  ›  专栏  ›  技术社区  ›  RTC222

nasm中的rdtscp始终返回相同的值

  •  2
  • RTC222  · 技术社区  · 7 年前

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

    我阅读了Gabriele Paoloni在Intel(2010年9月)和其他Web资源(其中大部分是C中的示例)上的“如何在Intel IA-32和IA-64指令集体系结构上基准代码执行时间”。

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

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

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

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

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

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

    所以我的问题是:

    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个,但听起来循环太多了。

    1 回复  |  直到 7 年前
        1
  •  4
  •   Peter Cordes    7 年前

    您的第一个代码(导致标题问题)是错误的,因为它覆盖了 rdtsc rdtscp 结果与 cpuid 结果为EAX、EBX、ECX和EDX。

    使用 lfence 而不是 CPUID ;在Intel和启用了Spectre缓解的AMD上, 篱笆 将序列化指令流,从而使用 RDSTC .


    记住,RDTSC计算参考周期,而不是核心时钟周期。 Get CPU cycle count? 关于RDTSC的更多信息。

    你没有 CPUID 篱笆 在你的测量间隔内。但是你 RDTSCP 在测量间隔内。背靠背 RDTSCP 不快, 如果在没有预热CPU的情况下运行,64个参考周期听起来完全合理。空闲时钟速度通常比参考周期慢得多。 ;1个参考周期等于或接近Intel CPU上的“贴纸”频率,例如最大非涡轮持续频率。例如,“4GHz”Skylake CPU上的4008兆赫。


    这不是一条指令的时间安排

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

    不过,您可以尝试减去测量开销。例如 clflush to invalidate cache line via C function . 另见以下内容: Using time stamp counter and clock_gettime for cache miss Memory latency measurement with time stamp counter .


    这是我通常用来分析短块指令的延迟或吞吐量(以及Uops融合域和未融合域) . 调整您如何使用它来解决延迟瓶颈问题,比如这里,或者,如果您只想测试吞吐量,就不要这样做。例如用 %rep 使用足够多的不同寄存器阻塞以隐藏延迟,或使用 pxor xmm3, xmm3 经过一个短的街区,让无序的执行工作其魔力。(只要你不在前端遇到瓶颈。)

    您可能希望使用NASM的smartalign包,或者使用yasm,以避免对align指令使用单字节nop指令墙。nasm默认为非常愚蠢的nop,即使在64位模式下,长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 , 每次更改源代码时都可以向上箭头并重新运行 :

    (实际上,我把nasm+ld+可选的disassemble放到了一个shell脚本中,该脚本名为 asm-link ,以便在不分析时保存键入内容。分解确保循环中的内容就是 意味 特别是如果你有 %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"
    

    3.9GHz下I7-6700K的结果 (电流) 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个循环=每4个链16个循环 ADSD =4周期延迟 ADSD ,重新生成Agner Fog的4个周期的测量结果,以超过100分之一的比例,即使对于这个包含少量启动开销和中断开销的测试也是如此。

    选择不同的计数器进行记录。添加一个 :u ,像 instructions:u 对于性能,甚至只计算用户空间指令,不包括在中断处理程序期间运行的任何指令。我通常不这样做,所以我可以把这看作是对挂钟时间的解释的一部分。但如果你这样做了, cycles:u 可以匹配 非常 紧密联系 说明:U .

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

    调整初始ECX值,使总时间约为0.1到1秒,这通常很充足,尤其是当CPU快速上升到最大涡轮转速时(例如,Skylake具有硬件P状态和相当积极的能量性能偏好)。或禁用涡轮增压的最大非涡轮增压。

    但这是以核心时钟周期为单位的,而不是以参考周期为单位的,因此不管CPU频率如何变化,它仍然会给出相同的结果。 . (+-转换过程中时钟停止时发出的一些噪音。)