Performance Haswell AVX/FMA延迟测试比英特尔指南中说的慢1个周期

Performance Haswell AVX/FMA延迟测试比英特尔指南中说的慢1个周期,performance,x86-64,intel,cpu-architecture,avx,Performance,X86 64,Intel,Cpu Architecture,Avx,在《英特尔Intrinsics指南》中,vmulpd和vfmadd213pd的延迟为5,vaddpd的延迟为3 我写了一些测试代码,但是所有的结果都慢了一个周期 以下是我的测试代码: .CODE test_latency PROC vxorpd ymm0, ymm0, ymm0 vxorpd ymm1, ymm1, ymm1 loop_start: vmulpd ymm0, ymm0, ymm1 vmulpd ymm0, ymm0, ymm1 v

在《英特尔Intrinsics指南》中,vmulpd和vfmadd213pd的延迟为5,vaddpd的延迟为3

我写了一些测试代码,但是所有的结果都慢了一个周期

以下是我的测试代码:

.CODE
test_latency PROC
    vxorpd  ymm0, ymm0, ymm0
    vxorpd  ymm1, ymm1, ymm1

loop_start:
    vmulpd  ymm0, ymm0, ymm1
    vmulpd  ymm0, ymm0, ymm1
    vmulpd  ymm0, ymm0, ymm1
    vmulpd  ymm0, ymm0, ymm1
    sub     rcx, 4
    jg      loop_start

    ret
test_latency ENDP
END
包括 包括 包括 包括 外部空隙试验延迟64分钟; int main { SetThreadAffinityMaskGetCurrentThread,1;//避免上下文切换 int64_t n=int64_t3e9; 双启动=omp\u get\u时间; 试验_latencyn; 双端=omp\u get\u时间; 双倍时间=结束-开始; double freq=3.3e9;//我的CPU频率 双延迟=频率*时间/n; 打印平坦=%f\n,延迟; } 我的CPU是核心i5 4590,我把它的频率锁定在3.3GHz。输出为:延迟=6.102484

奇怪的是,如果我将vmulpd ymm0,ymm0,ymm1更改为vmulpd ymm0,ymm0,ymm0,那么输出变成:latency=5.093745

有什么解释吗?我的测试代码有问题吗

更多结果

我猜

我更改了测试延迟,如下所示:

.CODE
test_latency PROC
    vxorpd  ymm0, ymm0, ymm0
    vxorpd  ymm1, ymm1, ymm1

loop_start:
    vaddpd  ymm1, ymm1, ymm1  ; added this line
    vmulpd  ymm0, ymm0, ymm1
    vmulpd  ymm0, ymm0, ymm1
    vmulpd  ymm0, ymm0, ymm1
    vmulpd  ymm0, ymm0, ymm1
    sub     rcx, 4
    jg      loop_start

    ret
test_latency ENDP
END
最后得到了5个循环的结果。还有其他说明可以达到相同的效果:

vmovupd     ymm1, ymm0
vmovupd     ymm1, [mem]
vmovdqu     ymm1, [mem]
vxorpd      ymm1, ymm1, ymm1
vpxor       ymm1, ymm1, ymm1
vmulpd      ymm1, ymm1, ymm1
vshufpd     ymm1, ymm1, ymm1, 0
但这些指示不能:

vmovupd     ymm1, ymm2  ; suppose ymm2 is zeroed
vpaddq      ymm1, ymm1, ymm1
vpmulld     ymm1, ymm1, ymm1
vpand       ymm1, ymm1, ymm1
对于ymm指令,我想避免1个额外循环的条件是:

所有输入都来自同一个域。 所有输入都足够新鲜。“从旧值移动”不起作用 至于VEX xmm,条件似乎有点模糊。它似乎与上半个州有关,但我不知道哪一个更干净:

vxorpd      ymm1, ymm1, ymm1
vxorpd      xmm1, xmm1, xmm1
vzeroupper

这对我来说是个很难回答的问题。

自从在Skylake上注意到这件事以来,几年来我一直想写一些关于这件事的东西

绕过延迟延迟是有粘性的:整数SIMD指令可能会感染将来读取该值的所有指令,即使在指令执行很长时间后也是如此。我很惊讶,这种感染在归零习惯用法中幸存了下来,特别是像vxorpd这样的FP归零指令,但我可以在SKL i7-6700k上重现这种影响,在Linux上使用perf直接在测试循环中计算时钟周期,而不是在时间和频率上乱搞

在Skylake上,似乎在循环正常工作之前,一行有3条或更多vxorpd归零指令,消除了额外的旁路延迟。另外,xor归零总是被消除的,不像mov消除有时会失败。但也许区别只是在后端的vpaddb发行和第一个vmulpd发行之间产生了一个缺口;在我的测试循环中,我在循环之前弄脏/污染了寄存器

更新:现在再次尝试我的测试代码,即使是一个vxorps似乎也能清除寄存器。也许微码更新改变了什么

可能调用方先前使用的YMM1涉及整数指令。TODO:调查寄存器进入这种状态的情况有多普遍,以及它何时能够在异或归零后存活!我希望只有在使用整数指令构造FP位模式时才会发生这种情况,包括vpcmpeqd ymm1、ymm1、ymm1之类的东西,以使a-NaN都是一位

在Skylake上,我可以通过在循环之前,在xor归零之后,执行vaddpd ymm1,ymm1,ymm1来修复它。或之前;可能没关系!这可能更为理想,将其放在前一个dep链的末尾,而不是此链的开头

正如我所写

xsave/rstor可以解决使用 像padd这样的SIMD整数指令会无限期地产生额外的延迟 用于使用FP指令读取,会影响两种指令的延迟 投入。e、 g.padd xmm0,xmm0然后在一个循环中addps xmm1,xmm0有5c 延迟,而不是通常的4,直到下一次保存/恢复

它是 绕过延迟,但即使不触摸寄存器也会发生 直到padd通过使用>ROB进行填充而完全退出 循环之前的UOP

测试程序: Perf在i7-6700k上生成静态可执行文件:

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

            129.01 msec task-clock                #    0.998 CPUs utilized            ( +-  0.51% )
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.016 K/sec                  
       500,053,798      cycles                    #    3.876 GHz                      ( +-  0.00% )
        50,000,042      branches                  #  387.576 M/sec                    ( +-  0.00% )
       200,000,059      instructions              #    0.40  insn per cycle           ( +-  0.00% )
       150,020,084      uops_issued.any           # 1162.883 M/sec                    ( +-  0.00% )
       150,014,866      uops_executed.thread      # 1162.842 M/sec                    ( +-  0.00% )

          0.129244 +- 0.000670 seconds time elapsed  ( +-  0.52% )

50米迭代的500米循环=10个循环对2个VADDP进行依赖,或每个循环5个。

我尝试在vxorpd之前或之后添加vaddpd ymm1、ymm1、ymm1,但vmulpd ymm0、ymm0、ymm1的延迟仍然是6。@kevinjwz:不幸的是,我没有一个可以测试的Haswell系统,但我可以在Skylake上重新进行测试。vpaddb ymm1,ymm1,ymm1在循环感染寄存器之前,使其变慢。vaddpd ymm1,ymm1,ymm1在这之后,使其再次快速每vmulpd 4个周期;Skylake为mul/add/FMA提供了4c延迟,放弃了Haswell拥有的3c延迟专用FP-add单元。我可以确认vxorpd在vpaddb之后的归零不会清除寄存器!!不过,FP shuffle与vunpcklpd类似。或3次或3次以上的异或归零重复。非常神秘。re:在Skylake上,似乎在循环正常工作之前,有3条或更多的vxorpd归零指令,消除了额外的旁路延迟。你用1x vxorpd+nop fill测试过它是否真的只是分离解码组吗?@Noah:没有,我还没有。你能在你的威士忌湖机器上重新设定效果吗?和/或冰
Lake?你能把基准代码发布到某个地方吗?我可以试试。你进一步的测试都表明,如果你读一个寄存器而不写它,它的额外延迟属性可以保留整个循环,通过另一个操作数影响依赖链。而且vzeroupper可以清除Haswell上的这个属性。Skylake上没有。@PeterCordes实际上vzeroupper只能更改vmulpd xmm0、xmm0、xmm1的延迟;它不会对vmulpd ymm0、ymm0、ymm1进行更改。所以我还是很好奇,很有趣。在Skylake上,vzeroupper也不修复xmm,如果只读寄存器被污染,速度仍然很慢。但是vzeroupper有着不同的实现细节,这也导致了它的不同。
; taskset -c 3 perf stat --all-user -etask-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread -r1 ./bypass-latency

default rel
global _start
_start:
    vmovaps   xmm1, [one]        ; FP load into ymm1 (zeroing the upper lane)
    vpaddd    ymm1, ymm1,ymm0   ; ymm1 written in the ivec domain
    ;vxorps    ymm1, ymm1,ymm1   ; In 2017, ymm1 still makes vaddps slow (5c) after this
    ; but I can't reproduce that now with updated microcode.
    vxorps    ymm0, ymm0, ymm0   ; zeroing-idiom on ymm0
    mov       rcx, 50000000

align 32  ; doesn't help or hurt, as expected since the bottleneck isn't frontend
.loop:
    vaddps  ymm0, ymm0,ymm1
    vaddps  ymm0, ymm0,ymm1
    dec     rcx
    jnz .loop

    xor edi,edi
    mov eax,231
    syscall      ; exit_group(0)

section .rodata
align 16
one:            times 4 dd 1.0
 Performance counter stats for './foo' (4 runs):

            129.01 msec task-clock                #    0.998 CPUs utilized            ( +-  0.51% )
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.016 K/sec                  
       500,053,798      cycles                    #    3.876 GHz                      ( +-  0.00% )
        50,000,042      branches                  #  387.576 M/sec                    ( +-  0.00% )
       200,000,059      instructions              #    0.40  insn per cycle           ( +-  0.00% )
       150,020,084      uops_issued.any           # 1162.883 M/sec                    ( +-  0.00% )
       150,014,866      uops_executed.thread      # 1162.842 M/sec                    ( +-  0.00% )

          0.129244 +- 0.000670 seconds time elapsed  ( +-  0.52% )