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

Why adding vmovapd instruction makes simd vectorized code run faster?

提问人:Rasmus 提问时间:10/24/2023 最后编辑:Rasmus 更新时间:10/24/2023 访问量:74

问:

我正在对一些高性能数值代码进行矢量化,我注意到使用 Intel 的 SSE、AVX 和 AVX512 指令的 SIMD 矢量化性能与笔记本电脑上矢量寄存器的长度不成比例。我的笔记本电脑有 Tiger Lake 架构。我希望 AVX 的速度是 SSE 的两倍左右,AVX512 的速度是 AVX 的两倍左右。这是 x86-64 汇编中的一个玩具示例,类似于我正在开发的代码,其中注释掉了一条指令:

AVX512测试.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.sld -o AVX512test.x AVX512.otime ./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 操作,计算速度会明显变慢,但另一方面,正如我所期望的那样,它会随着矢量化而扩展。

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

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

组装 SIMD 微基准测试 AVX512

评论

1赞 Peter Cordes 10/24/2023
vmovapd %zmm17, %zmm19通过 ZMM19 断开循环携带的依赖链。将您的 asm 复制/粘贴到 uica.uops.info 中,让它针对 Tiger Lake 对其进行分析,并生成依赖关系图。

答:

4赞 fuz 10/24/2023 #1

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 只能运行一个 FMA。由于 AVX-512 的矢量宽度是其两倍,这意味着只要您的数值内核允许至少两个并行的 FMA 运算,它们每个周期执行相同数量的 FLOP。

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

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

评论

0赞 Rasmus 10/24/2023
谢谢!这回答了我的问题。我需要了解 ILP 以及什么是端口。顺便说一句,我认为您的意思是注释掉 vmovapd 指令会禁用并行性,而不是在您的答案中启用并行性,也许您可以编辑它。
0赞 fuz 10/24/2023
@Rasmes 不,注释掉(即删除)指令是实现并行性的原因,因为该指令是将第一个指令块耦合到第二个块的原因。没有指令,它们似乎是独立的,可以同时执行。如果解决了您的问题,请记得将我的答案标记为已接受。vfmadd213pd
1赞 Rasmus 10/24/2023
基准测试显示,包含指令可以使代码更快。
1赞 fuz 10/24/2023
@Rasmus 看来我错了!也许情况恰恰相反。尝试使用像 uica.uops.info 这样的微架构模拟器。它准确地显示哪些指令在哪些端口上运行,以及并行度如何。
2赞 fuz 10/24/2023
好。。。现在我明白了。这两个移动刷新累加器并使用新值,使迭代彼此独立(即多个迭代可以同时运行)。如果没有第二次移动,则永远不会像这样刷新,并且第二个块将成为循环携带的依赖项。zmm18zmm19zmm19