assembly - 为什么添加 vmovapd 指令可以使 simd 矢量化代码运行得更快?

标签 assembly simd microbenchmark avx512

我正在尝试一些高性能数字代码的矢量化,我注意到使用英特尔的 SSE、AVX 和 AVX512 指令的 SIMD 矢量化的性能不会随着笔记本电脑上矢量寄存器的长度而变化。我的笔记本电脑采用 Tiger Lake 架构。我希望 AVX 的速度大约是 SSE 的两倍,AVX512 的速度大约是 AVX 的两倍。这是 x86-64 汇编中的玩具示例,类似于我正在开发的代码,其中注释掉了一条指令:

AVX512test.s

.globl _start
.text
_start:  
  xor %edx, %edx
loop:
  vmovapd   %zmm17, %zmm18
  vfmadd213pd   %zmm5, %zmm6, %zmm18  
  vfmadd213pd   %zmm4, %zmm17, %zmm18 
  vfmadd213pd   %zmm3, %zmm17, %zmm18 
  vfmadd213pd   %zmm2, %zmm17, %zmm18 
  vfmadd213pd   %zmm1, %zmm17, %zmm18 
  vfmadd213pd   %zmm0, %zmm17, %zmm18 
#  vmovapd  %zmm17, %zmm19
  vfmadd213pd   %zmm15, %zmm16, %zmm19
  vfmadd213pd   %zmm14, %zmm17, %zmm19
  vfmadd213pd   %zmm13, %zmm17, %zmm19
  vfmadd213pd   %zmm12, %zmm17, %zmm19
  vfmadd213pd   %zmm11, %zmm17, %zmm19
  vfmadd213pd   %zmm10, %zmm17, %zmm19
  vfmadd213pd   %zmm9, %zmm17, %zmm19 
  vfmadd213pd   %zmm8, %zmm17, %zmm19 
  vfmadd213pd   %zmm7, %zmm17, %zmm19 
  vdivpd    %zmm19, %zmm18, %zmm18
  inc %edx
  cmp $10000000, %edx
  jne loop
  movq $60, %rax
  syscall

对于 AVX,我有相同的代码,其中 zmm 被替换为 ymm,循环长度设置为 20000000;对于 SSE,我有相同的代码,其中 zmm 被替换为 xmm,循环长度设置为 40000000。如果我取消注释 vmovapd 操作,请将其汇编使用 as -o AVX512test.o AVX512test.s ,将其与 ld -o AVX512test.x AVX512.o 链接并使用 time ./AVX512test.x 运行 ,我得到:

上海证券交易所

real    0m0.090s
user    0m0.089s
sys 0m0.000s

AVX

real    0m0.050s
user    0m0.049s
sys 0m0.000s

AVX512

real    0m0.058s
user    0m0.058s
sys 0m0.000s

因此,从 SSE 到 AVX 可以很好地扩展,但从 AVX 到 AVX512 则不然。

如果我将 vmovapd 指令注释掉,我会得到:

上海证券交易所


real    0m0.351s
user    0m0.351s
sys 0m0.000s

AVX

real    0m0.189s
user    0m0.189s
sys 0m0.000s

AVX512

real    0m0.109s
user    0m0.109s
sys 0m0.000s

因此,如果没有第二个 vmovapd 操作,计算速度会明显变慢,但另一方面,正如我所期望的那样,它会随着矢量化而扩展。

我的问题是为什么删除操作会使代码运行速度变慢,为什么使用 vmovapd 操作时 AVX512 没有比 AVX 更快?

我尝试以不同的方式修改代码,例如在循环体内使用越来越少的指令并让所有指令相同。但我发现唯一对性能和矢量寄存器长度缩放有显着影响的是其他指令中是否有 vmovapd 指令。我有点无能为力,但想知道这是否与乱序执行有关。如果可能的话,我希望在 512 位寄存器上矢量化的代码速度大约是在未注释 vmovapd 指令的情况下在 256 位寄存器上矢量化的代码的两倍。

最佳答案

100 毫秒对于一个好的基准来说太短了。至少一两秒钟。也就是说,使用您注释掉的指令,针对 zmm19 的指令可以与之前的指令并行运行,而没有该指令则不能。这解释了总持续时间的差异。

在许多微架构上,AVX-512 可以在比 AVX 和 SSE 更少的端口上运行。具体来说,AVX 和 SSE 指令可以在端口 p0、p1 和 p5 上运行,而 AVX-512 一起使用端口 p0 和 1(另一个非 SIMD 指令可以在 p1 上运行,而 p0+p1 用于 AVX-512)或 p5 .

FMA 指令在 Tigerlake 客户端上的端口 p0 和 p1 上运行。这意味着使用 SSE 和 AVX,每个周期可以运行两个 FMA,而使用 AVX-512 时只能运行一个。由于 AVX-512 的向量宽度是两倍,这意味着只要您的数值内核允许并行至少两个 FMA 操作,它们每个周期执行相同数量的 FLOP。

如果您按照指示保留移动指令,则您的代码就是这种情况,然后循环的多次迭代是独立的。正如您所观察到的,AVX2 和 AVX-512 的性能将非常相似。但如果您注释掉该指令,现在迭代将通过依赖链耦合,并且必须按顺序执行。如果您的代码每个周期仅限一个 FMA,AVX-512 将比 AVX2 更快。

我的建议:优化代码以获得更高的指令级并行性 (ILP)。 AVX-512 是此应用程序的不错选择,您应该继续使用它。当您在服务器 CPU(Xeon Gold 级)上执行此代码时,p01 和 p5 都可以执行 FMA 指令,并且一旦您重写代码以允许更高的 ILP,使用 AVX-512 代码将会更快。

关于assembly - 为什么添加 vmovapd 指令可以使 simd 矢量化代码运行得更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77347553/

相关文章:

visual-c++ - 如何让 VC 编译器更好地优化我的 SIMD 代码?

java - 为什么 Java 中 try/catch 与其他形式的控制流的性能存在差异?

c++ - Hook 内部函数 : How do the the parameters look like?

c - 有没有什么可以阻止 CPU 在执行其预定的设置操作之前等待很长时间?

c - Sparc V8比较和交换函数的内联汇编实现

assembly - x86 程序集 16 位相对调用

c - 如何判断内存是否对齐?

sse - 检查比较结果的多个向量中的每个向量中至少有1个元素为真-水平或然后与

c - 如何在 RDMA 上构建性能测试和运行延迟测试

java - 我的 python 程序比同一程序的 java 版本执行得更快。是什么赋予了?