performance - Haswell AVX/FMA 延迟测试比英特尔指南慢 1 个周期

标签 performance x86-64 intel cpu-architecture avx

在英特尔内部函数指南中,vmulpdvfmadd213pd延迟为 5,vaddpd延迟为 3。

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

这是我的测试代码:

.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
#include <stdio.h>
#include <omp.h>
#include <stdint.h>
#include <windows.h>

extern "C" void test_latency(int64_t n);

int main()
{
    SetThreadAffinityMask(GetCurrentThread(), 1);   // Avoid context switch
    
    int64_t n = (int64_t)3e9;
    double start = omp_get_wtime();
    test_latency(n);
    double end = omp_get_wtime();
    double time = end - start;
    
    double freq = 3.3e9;    // My CPU frequency
    double latency = freq * time / n;
    printf("latency = %f\n", latency);
}

我的CPU是Core i5 4590,我将其频率锁定在3.3GHz。输出为:latency = 6.102484 .

奇怪的是,如果我改变 vmulpd ymm0, ymm0, ymm1vmulpd ymm0, ymm0, ymm0 ,则输出变为:latency = 5.093745 .

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

更多结果

results on Core i5 4590 @3.3GHz
vmulpd  ymm0, ymm0, ymm1       6.056094
vmulpd  ymm0, ymm0, ymm0       5.054515
vaddpd  ymm0, ymm0, ymm1       4.038062
vaddpd  ymm0, ymm0, ymm0       3.029360
vfmadd213pd ymm0, ymm0, ymm1   6.052501
vfmadd213pd ymm0, ymm1, ymm0   6.053163
vfmadd213pd ymm0, ymm1, ymm1   6.055160
vfmadd213pd ymm0, ymm0, ymm0   5.041532

(without vzeroupper)
vmulpd  xmm0, xmm0, xmm1       6.050404
vmulpd  xmm0, xmm0, xmm0       5.042191
vaddpd  xmm0, xmm0, xmm1       4.044518
vaddpd  xmm0, xmm0, xmm0       3.024233
vfmadd213pd xmm0, xmm0, xmm1   6.047219
vfmadd213pd xmm0, xmm1, xmm0   6.046022
vfmadd213pd xmm0, xmm1, xmm1   6.052805
vfmadd213pd xmm0, xmm0, xmm0   5.046843

(with vzeroupper)
vmulpd  xmm0, xmm0, xmm1       5.062350
vmulpd  xmm0, xmm0, xmm0       5.039132
vaddpd  xmm0, xmm0, xmm1       3.019815
vaddpd  xmm0, xmm0, xmm0       3.026791
vfmadd213pd xmm0, xmm0, xmm1   5.043748
vfmadd213pd xmm0, xmm1, xmm0   5.051424
vfmadd213pd xmm0, xmm1, xmm1   5.049090
vfmadd213pd xmm0, xmm0, xmm0   5.051947

(without vzeroupper)
mulpd   xmm0, xmm1             5.047671
mulpd   xmm0, xmm0             5.042176
addpd   xmm0, xmm1             3.019492
addpd   xmm0, xmm0             3.028642

(with vzeroupper)
mulpd   xmm0, xmm1             5.046220
mulpd   xmm0, xmm0             5.057278
addpd   xmm0, xmm1             3.025577
addpd   xmm0, xmm0             3.031238

我的猜测

我改变了test_latency像这样:

.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 个额外周期的条件是:

  1. 所有输入都来自同一域。
  2. 所有输入都足够新鲜。 (从旧值移动不起作用)

至于VEX xmm,情况似乎有点模糊。好像和上半状态有关,但不知道哪个更干净:

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

这个问题对我来说很难。

最佳答案

自从在 Skylake 上注意到它以来,我几年来一直想写一些关于这个的东西。 https://github.com/travisdowns/uarch-bench/wiki/Intel-Performance-Quirks#after-an-integer-to-fp-bypass-latency-can-be-increased-indefinitely

旁路延迟延迟是“粘性的”:整数 SIMD 指令可以“感染”读取该值的所有 future 指令,即使在指令完成很长时间之后也是如此。我很惊讶“感染”在归零惯用法中幸存下来,尤其是像 vxorpd 这样的 FP 归零指令。 ,但我可以在 SKL 上重现这种效果(i7-6700k,在 Linux 上使用 perf 直接在测试循环中计算时钟周期,而不是搞乱时间和频率。)

(在 Skylake 上,在循环发生工作之前,似乎有 3 个或更多 vxorpd 连续归零指令,从而消除了额外的旁路延迟。据我所知,异或归零总是被消除,这与mov-elimination 有时会失败。但也许区别只是在后端的 vpaddb 和第一个 vmulpd 之间创建了一个间隙;在我的测试循环中,我“脏”/污染了之前的寄存器循环。)

(更新:现在再次尝试我的测试代码,即使是 vxorps 似乎也清理了寄存器。也许微代码更新改变了一些东西。)

推测调用者中 YMM1 的某些先前使用涉及整数指令。 (TODO:调查寄存器进入这种状态的常见情况,以及它何时可以在异或归零中幸存!我预计它只会在使用整数指令构造 FP 位模式时发生,包括像 vpcmpeqd ymm1,ymm1,ymm1 这样的东西创建一个 -NaN(全 1 位)。)

在 Skylake 上,我可以通过执行 vaddpd ymm1, ymm1, ymm1 来修复它循环之前,异或归零之后。 (或者之前;这可能并不重要!这可能是更优化的,将其放在前一个 dep 链的末尾而不是这个的开头。)


正如我所写的in a comment on another question

xsave/rstor can fix the issue where writing a register with a SIMD-integer instruction like paddd creates extra latency indefinitely for reading it with an FP instruction, affecting latency from both inputs. e.g. paddd xmm0, xmm0 then in a loop addps xmm1, xmm0 has 5c latency instead of the usual 4, until the next save/restore.

It's bypass latency but still happens even if you don't touch the register until after the paddd has definitely retired (by padding with >ROB uops) before the loop.


测试程序:

; 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

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% )

500M 迭代的 500M 周期 = 2x 10 周期循环携带依赖 vaddps ,或各 5 个。

关于performance - Haswell AVX/FMA 延迟测试比英特尔指南慢 1 个周期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64116679/

相关文章:

css - 指定 element.class { ... } 与仅指定 .class { ... } 的性能提升?

performance - PostgreSQL:多表一个条件

c# 多线程内存使用 - 应用程序变慢和 CPU 使用率下降

assembly - 使用相同的小正数有效加载 RAX 和 R8

memory - 直接内存映射到 DIMM

c - 为什么 _mm_permute_ps 的最后一个参数是一个 int?

c# - .NET 4.5.1 可以在 core2duo 处理器上运行吗?

c++ - 更短的循环,相同的覆盖范围,为什么我在使用 Visual Studio 2013 的 C++ 中得到更多的末级缓存未命中?

linux - 在 x86_64 AT&T 中调用 scanf 时出现段错误

java - Android 和 Iphone 中的 3D 效果