代码之家  ›  专栏  ›  技术社区  ›  Or Yaacov

汇编—如何根据延迟和吞吐量对CPU指令进行评分

  •  2
  • Or Yaacov  · 技术社区  · 7 年前

    我正在寻找一种公式/方法来衡量一条指令的速度,或者更具体地说,按CPU周期给每条指令一个“分数”。

    以follow汇编程序为例,

    nop                     
    mov         eax,dword ptr [rbp+34h] 
    inc         eax     
    mov         dword ptr [rbp+34h],eax  
    

    mov r,m:吞吐量=0.5延迟=2

    移动m,r

    inc:吞吐量=0.25延迟=1

    我知道程序中指令的顺序在这里很重要,但是 我想创造一些通用的东西,不需要“精确到单个周期”

    有人知道我该怎么做吗?

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

    没有什么公式可以应用,你必须衡量 .

    同一uarch系列的不同版本上的相同指令可能具有不同的性能。例如 mulps

    • Sandybridge 1c/5c吞吐量/延迟。
    • HSW 0.5/5。BDW 0.5/3(FMA装置中更快的乘法路径?FMA仍然是5c)。
    • SKL0.5/4(也有较低的延迟FMA)。SKL运行 addps

    如果不测量或者不知道一些微体系结构的细节,你就不可能预测这些。我们期望FP-math操作不会是单周期延迟,因为它们比整数操作复杂得多。(因此,如果它们是单周期,则时钟速度对于整数运算设置得太低。)


    通过在展开的循环中多次重复该指令进行度量。或者 完全地 展开时没有循环,但这样您就可以打败uop缓存并获得前端瓶颈。(例如,解码10字节 mov r64, imm64

    https://uops.info/ 已经自动化了这个测试 对于每一条(非特权)指令的每一种形式,您甚至可以单击任何表项来查看它们使用了什么测试循环。e、 g.天湖 xchg r32, eax https://uops.info/html-lat/SKL/XCHG_R32_EAX-Measurements.html )从每个输入操作数到每个输出。(EAX->R8D的2个周期延迟,但是R8D->EAX的1个周期延迟)所以我们可以猜测 the 3 uops include copying EAX to an internal temporary

    https://uops.info/

    Agner Fog通过对重复一条指令的大型非循环代码块计时来创建他的指令表(您似乎正在读取)。 https://agner.org/optimize/ . 他的指令表的简介部分简要解释了他是如何测量的,他的微体系指南解释了不同x86微体系结构如何在内部工作的更多细节。不幸的是,在他手工编辑的表格中,偶尔会出现打字错误或复制/粘贴错误。

    http://instlatx64.atw.hu/ 也有实验测量的结果。我认为他们使用了一种类似的技术,即重复一大块相同的指令,可能小到可以放入uop缓存。但它们不使用性能计数器来衡量每条指令需要什么执行端口,因此它们的吞吐量数字不能帮助您确定哪些指令与哪些其他指令竞争。

    后两个来源已经存在了一个多世纪了uops.info公司,并覆盖一些较老的CPU,尤其是较老的AMD。


    为了自己测量延迟,可以将每条指令的输出作为下一条指令的输入。

     mov  ecx, 10000000
     inc_latency:
         inc eax
         inc eax
         inc eax
         inc eax
         inc eax
         inc eax
    
         sub ecx,1          ; avoid partial-flag false dep for P4
         jnz inc_latency    ; dec or sub/jnz macro-fuses into 1 uop on Intel SnB-family
    

    这个依赖链是7 inc 7 * inc_latency 循环。使用核心时钟周期(不是RDTSC周期)的性能计数器,可以轻松地测量 全部的 迭代到10k中的1部分,更加小心可能比这更精确。10000000的重复计数隐藏了您使用的任何时间的启动/停止开销。

    我通常在Linux静态可执行文件中放入这样一个循环,只会生成一个 sys_exit(0) syscall perf stat ./testloop 得到时间和循环计数。(见 Can x86's MOV really be "free"? Why can't I reproduce this at all?

    Understanding the impact of lfence on a loop with two long dependency chains, for increasing lengths ,增加了使用的复杂性 lfence 为两个dep链排出无序执行窗口。


    为了测量吞吐量,您可以使用单独的寄存器,和/或偶尔包含一个异或归零来打破dep链,并让无序的exec重叠。 不要忘了使用perf计数器来查看它可以在哪些端口上运行,这样您就可以知道它将与哪些其他指令竞争。(例如,FMA(p01)和shuffles(p5)根本不争夺Haswell/Skylake上的后端资源,只争夺前端吞吐量。)也别忘了测量前端uop计数:有些指令会解码以增加uop。

    例如,如果FP multiply的吞吐量为0.25c(每个时钟4个),那么我们可以在Haswell上同时保持20个(5c延迟)。这比我们拥有的寄存器还多,所以我们可以使用所有16个寄存器,发现实际上吞吐量只有0.5c xorps xmm0,xmm0 偶尔会让无序执行与某些块重叠。

    更多通常是更好的;仅仅有足够的时间来隐藏延迟可能会因为不完美的调度而减慢速度。如果我们想疯了

     mov  ecx, 10000000
     inc_latency:
       %rep 10          ;; source-level repeat of a block, no runtime branching
         inc eax
         inc ebx
         ; not ecx, we're using it as a loop counter
         inc edx
         inc esi
         inc edi
         inc ebp
         inc r8d
         inc r9d
         inc r10d
         inc r11d
         inc r12d
         inc r13d
         inc r14d
         inc r15d
       %endrep
    
         sub ecx,1          ; break partial-flag false dep for P4
         jnz inc_latency    ; dec/jnz macro-fuses into 1 uop on Intel SnB-family
    

    xor eax,eax 一个让OoO exec不仅仅是在什么时候重叠的地方 sub 写入所有标志。(见 INC instruction vs ADD 1: Does it matter? )

    在测量的吞吐量和延迟时也有类似的问题 shl r32, cl shl 背靠背通过标志和寄存器创建依赖关系。(或者对于吞吐量,甚至没有寄存器dep)。

    我在Agner Fog的博客上发表了这篇文章: https://www.agner.org/optimize/blog/read.php?i=415#860 . 我混合了 shl edx,cl 和四个在一起 add edx,1 指令,看看增加一条指令有什么好处,其中标志依赖性不是问题。在SKL上,它平均只慢了1.23个周期,所以真正的延迟成本 只有~1.23个周期,而不是2个周期。(由于运行 ,我想。体重指数2 shlx edx, edx, ecx


    相关:有关整个代码块(包含不同指令)的静态性能分析,请参阅 What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand? . (它使用“延迟”这个词来表示整个计算的端到端延迟,但实际上是询问一些小到足以让OoO exec重叠不同部分的事情,因此指令延迟和吞吐量都很重要。)


    这个 Latency=2 加载/存储的数字似乎来自Agner Fog的指令表( https://agner.org/optimize/ mov rax, [rax] . 你会发现那是4c 延迟,如果你把它放在一个循环。