为什么 Tigerlake 的 gcc 自动矢量化使用 ymm 而不是 zmm 寄存器

Ral*_*alf 3 c gcc avx auto-vectorization avx512

我想探索 gcc (10.3) 的自动矢量化。我有以下简短的程序(请参阅https://godbolt.org/z/5v9a53aj6),它计算向量所有元素的总和:

#include <stdio.h>
#define LEN 1024

// -ffast-math -march=tigerlake -O3 -fno-unroll-loops
  
int
main()
{
  float v[LEN] __attribute__ ((aligned(64)));
  float s = 0;
  for (unsigned int i = 0; i < LEN; i++) s += v[i];
  printf("%g\n", s);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我用选项编译-ffast-math -march=tigerlake -O3 -fno-unroll-loops。由于 Tigerlake 处理器具有 avx512,我希望 gcc 自动向量化使用 zmm 寄存器,但它实际上在最内层循环中使用 ymm 寄存器(avx/avx2):

vaddps  ymm0, ymm0, YMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)

如果我替换-march=tigerlake-mavx512f,则使用 zmm 寄存器:

vaddps  zmm0, zmm0, ZMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)

如果我只是指定,为什么不使用 zmm 寄存器-march=tigerlake

Pet*_*des 9

-march=tigerlake默认为 ,-mprefer-vector-width=256因为与其他 AVX-512 功能(如屏蔽和新指令)不同,实际使用 512 位向量需要权衡。

对于您希望受益的程序,请尝试使用-mprefer-vector-width=512. (以及所有相同的其他选项,例如-march=native -O3 -flto -ffast-mathor -fno-math-errno -fno-trapping-math,最好是-fprofile-generate/ -fprofile-use。)

在您的情况下,您主要会遇到页面错误的瓶颈,因为您在一些未初始化的堆栈内存上循环,仅循环一次而没有预热。(或者你的循环时间太短。)我希望这只是演示它如何自动矢量化,而不是微基准。
绩效评估的惯用方式?


大多数程序将大部分时间花费在不自动矢量化的代码中,因此默认情况下降低最大涡轮增压是不值得的。请参阅降低 CPU 频率的 SIMD 指令

在 Ice Lake 客户端(非服务器)CPU 上,频率下降幅度很小,但在大多数 CPU 上仍然存在,因此如果一直以最大睿频运行,则在频率转换时至少会出现短暂的停顿。整个程序(包括非向量化代码)以及 CPU 上运行的其他任何程序的频率至少会下降几个百分点。

512 位向量的好处并不像您希望的 FP 吞吐量那么大:Ice/Tiger Lake 客户端 CPU 对于 512 位 FMA/add/mul 只有 1/时钟吞吐量(结合正常的两半) 256 位 FMA/add/mul 单元),端口 5 上没有某些 Skylake-X 和 Ice Lake Xeon CPU 具有的额外 512 位 FMA 单元。

(整数 SIMD 吞吐量有时可能会受益更多,因为大多数整数指令在 512 位时确实具有 2/时钟吞吐量。而不是像 256 位向量那样具有 3/时钟吞吐量;管道中存在任何 512 位 uop 都会禁用向量 ALU在端口 1 上,而不仅仅是 FMA 单元。因此 SIMD uop 吞吐量会降低,这可以降低具有良好计算强度且不会花费大量时间加载/存储的代码的加速。)

512 位向量对对齐更加敏感,即使是对于 DRAM 带宽瓶颈的循环(其中 256 位向量可以轻松跟上可用的核外带宽)。因此,在没有缓存阻塞的大型未对齐数组上,与循环中的 256 位向量相比,您可能会获得 10% 到 15% 的回归。对于 256 位向量,在循环遍历大数组时,未对齐数据的成本可能仅为对齐数据的 1% 或 2%。至少 SKX 是这样;我还没有听说 ICL / ICX 上的情况是否有所改变。

(当 L1d 缓存中的数据很热时,错位并不是很好;所有其他未对齐的负载都会损害缓存吞吐量。但是一些现实世界的代码没有很好地调整缓存阻塞,或者有一些部分不适合它,因此缓存未命中负载的性能也很重要。)

Glibc 的默认 malloc 喜欢通过从操作系统中获取一些新页面并使用前 16 个字节来记录有关它们的信息来进行大分配,因此您总是会遇到最坏的对齐情况ptr % 4096 == 16。如果仅使用 256 位向量,则所需的对齐方式为 64 或 32。


另请参阅编译器调整默认值的一些具体讨论,至少对于 clang,它们采用了-mprefer-vector-width=256与 GCC 相同的默认值-march=icelake-client

  • https://reviews.llvm.org/D111029#3674440 2021 年 10 月和 2022 年 6 月 - 讨论(不)增加 Ice Lake 客户端或服务器上的矢量宽度,因为频率损失较小。结果仍然不值得,在 Intel 对 clang与当前默认 256的测试中, Icelake Server 上的 SPEC CPU 2017 回归了 1% 。-mprefer-vector-width=512

  • https://reviews.llvm.org/D67259 2019 年关于决定跟随 GCC 的讨论,将 skylake-avx512、icelake-client 和icelake-server 等限制在 256 个(但当然不是 KNL,因为它没有甚至还有 AVX-512VL。)

  • 在 L1D 缓存之外,数据无论如何都会在 64B 缓存行中传输,因此(按一阶)AVX512 中的 512 位向量仅在几乎所有数据都在 L1D 缓存中时才有帮助。在这种情况下,512 位向量使每个周期的可用加载/存储 BW 和 SIMD 执行带宽加倍(考虑到频率降低时,略小于两倍)。对于来自 L2 缓存的数据,读取 BW 下降 2-3 倍,从而提供足够的额外周期来使用 256 位向量执行相同的算术。 (3认同)