从 Intel Xeon Phi 上的 AVX512 到 Intel i5-8259U 上的 AVX2 有什么损失?

What is lost in going from AVX512 on Intel Xeon Phi to AVX2 on Intel i5-8259U?

尝试遵循 course on Coursera,我尝试为我的 Intel i5-8259U CPU 优化示例 C++ 代码,我认为它支持 AVX2 SIMD 指令集。现在,AVX2 为每个内核提供 16 个寄存器(称为 YMM0YMM1、...、YMM15),它们的宽度为 256 位,这意味着每个内核最多可以处理同时处理 4 个双精度浮点数。与标量指令相比,利用 AVX2 SIMD 指令应该可以将我的代码优化为 运行 最多快 4 倍。

在链接课程中,您可以尝试 运行在支持 AVX512 使用 512 位宽寄存器的 Intel Xeon Phi 7210 (Knights Landing) 处理器上使用相同的代码进行数值积分。这意味着我们应该期望双精度运算的速度提高 8 倍。事实上,教师使用的代码获得了高达 14 倍的优化,几乎是 8 倍的 173%。额外的优化归功于 OpenMP。

为了 运行 我的 CPU 上的相同代码,我唯一更改的是传递给英特尔编译器的优化标志:而不是 -xMIC-AVX512,我使用 -xCORE-AVX2。由于仅在 256 位寄存器上进行 SIMD 矢量化,我获得的加速仅为 2 倍,仅为预期加速的 50%。将此 50% 与英特尔至强融核处理器上获得的 173% 进行比较。

为什么我只是从 AVX512 移动到 AVX2 就看到性能的急剧下降?当然,除了 SIMD 优化之外,还有其他因素在起作用。我错过了什么?


P.S。您可以在文件夹 integral/solutions/1-simd/ here.

中找到参考代码

TL:DR: KNL (Knight's Landing) 擅长运行ning代码专门为它编译,并因此获得更大的加速,因为它严重绊倒 运行ning "generic" 代码。

Coffee Lake 从 128 位 SSE2 到 256 位 AVX 仅获得 2 的加速,运行同时优化了 "generic" 和目标代码。

像Coffee Lake这样的主流CPU是"generic"现代编译器调优关心的目标之一,总体来说没有太多弱点。但是 KNL 不是;没有任何选项的 ICC 不关心 KNL


您假设加速的基线是 标量。但是如果没有 -march=native-xCORE-AVX2 之类的任何选项,英特尔的编译器 (ICC) 仍将 auto-vectorize 使用 SSE2,因为这是 x86-64 的基准。

-xCORE-AVX2 不会启用 auto-vectorization,它只会提供 auto-vectorization 更多指令供玩家使用。优化级别(包括 auto-vectorization)由 -O0 / -O2 / -O3 控制,对于 FP,由严格与快速 fp-model 控制。 Intel 的编译器默认使用 -fp-model fast=1 进行全面优化(比 fast=2 低一级),所以它类似于 gcc -O3 -ffast-math.

但是如果没有额外的选项,它只能使用基线 instruction-set,对于 x86-64 是 SSE2。这仍然比标量好。

SSE2 使用 128 位 XMM 寄存器进行压缩双精度运算,指令吞吐量与 AVX(在您的 i5 Coffee Lake 上)相同,但每条指令的工作量减半。 (而且它没有 FMA,因此编译器无法像使用 AVX+FMA 那样将源代码中的任何 mul+add 操作收缩为 FMA 指令)。

因此,对于纯粹是矢量 mul/add/FMA 瓶颈的简单问题,您的 Coffee Lake CPU 的速度提高了 2 倍正是您所期望的 SIMD 吞吐量(不是内存/缓存或其他任何东西)。

加速取决于您的代码在做什么。如果您遇到内存或缓存带宽瓶颈,更宽的寄存器只会稍微有助于更好地利用内存并行性并使其保持饱和。

并且 AVX + AVX2 添加了更强大的随机播放和混合以及其他很酷的东西,但是对于纯垂直 SIMD 的简单问题没有帮助。


所以真正的问题是 为什么 AVX512 在 KNL 上的帮助超过 4 倍? 在 Knight's Landing 上每个 AVX512 SIMD 指令有 8 double 个元素,从 2 个增加到如果指令吞吐量相同,SSE2 将提供 4 倍的预期加速。假设总指令数与 AVX512 相同。 (事实并非如此:对于相同的循环展开,每个循环开销的向量工作量随着向量变宽以及其他因素而增加。)

在不知道您正在编译的源代码的情况下很难确定。 AVX512 添加了一些可能有助于节省指令的功能,例如广播 memory-source 操作数,而不需要将单独的广播加载到寄存器中。

如果你的问题涉及到任何除法,KNL的FP除法full-precision非常慢,通常应该使用AVX512ER approximation instruction (28-bit precision) + Newton-Raphson 迭代(一对 FMA + mul)将其加倍,得到接近完整的 double(53 位有效数,包括 1 个隐式位)。 -xMIC-AVX512 启用 AVX512ER,并设置调整选项,以便 ICC 实际选择使用它。

(相比之下,Coffee Lake AVX 256 位除法吞吐量并不比 128 位除法吞吐量好多少,但如果没有 AVX512ER,就没有使用 Newton-Raphson 的有效方法double)。请参阅 Floating point division vs floating point multiplication - Skylake 编号适用于您的 Coffee Lake。


AVX / AVX512 可以避免额外的 movaps 指令来复制寄存器 ,这对 KNL 有很大帮助(每条指令不是 mul/add/FMA成本 FP 吞吐量,因为它有 2-per-clock FMA 但只有 2-per clock 最大指令吞吐量)。 (https://agner.org/optimize/)

KNL 基于 Silvermont low-power 内核(这就是他们如何将这么多内核安装到一个芯片上)。

相比之下,Coffee Lake 具有更强大的 front-end 和 back-end 执行吞吐量:它每个时钟停顿 2 个 FMA/mul/add,但每个时钟总指令吞吐量 4 个,因此有在不影响 FMA 吞吐量的情况下 运行 一些 non-FMA 指令的空间。


KNL (Xeon Phi)运行ning SSE/SSE2 指令的其他减速

KNL 是专门为 运行 AVX512 代码构建的。他们没有浪费晶体管,使其变得高效 运行ning 不是专门为它编译的遗留代码(使用 -xMIC-AVX512-march=knl)。

但是你的 Coffee Lake 是一个主流 desktop/laptop 核心,它必须快速 运行ning 任何过去或未来的二进制文件,包括仅使用 "legacy" SSE2 指令编码的代码,不是 AVX。

写入 XMM 寄存器的 SSE2 指令不修改相应 YMM/ZMM 寄存器的上层元素。 (XMM reg 是完整向量 reg 的低 128 位)。这将在当 运行 在支持更宽向量的 CPU 上使用遗留 SSE2 指令时,理论上会产生错误的依赖关系。 (主流英特尔 CPU 像 Sandybridge-family 通过模式转换避免这种情况,或者如果你没有正确使用 vzeroupper 则在 Skylake 上实际的错误依赖。参见 的比较2 种策略)。

KNL 显然有办法避免错误依赖:根据 Agner Fog 的测试 (in his microarch guide),他将其描述为 partial-register当您写入 AL 等整数寄存器时,重命名 P6 系列。当您阅读完整的寄存器时,您只会得到一个 partial-register 停顿。如果那是准确的,那么 SSE2 代码应该 运行 在 KNL 上正常,因为没有 AVX 代码读取 YMM 或 ZMM 寄存器。

(但是如果存在错误的依赖关系,循环中的 movaps xmm0, [rdi] 可能必须等到上一次迭代中写入 xmm0 的最后一条指令完成。这将击败 KNL 的适度 out-of-order 跨循环迭代重叠独立工作并隐藏负载 + FP 延迟的执行能力。)


当 运行 遗留 SSE/SSE2 指令时,也有可能在 KNL 上解码停顿:它会在超过 3 个前缀的指令上停顿,包括 0F 转义字节。因此,例如任何带有 REX 前缀的 SSSE3 或 SSE4.x 指令访问 r8..r15 或 xmm8..xmm15 都会导致解码停顿 5 到 6 个周期。

但是如果你省略了所有 -x / -march 选项,你将不会有那个,因为 SSE1/SSE2 + REX 仍然可以。只是(可选的 REX)+ 2 个其他前缀,用于 66 0F 58 addpd.

等指令

参见 Agner Fog 的微架构指南,在 KNL 章节中:16.2 指令获取和解码


OpenMP - 如果您希望 OpenMP 使用多线程,显然 KNL 有更多的内核。

但即使在一个物理内核中,KNL 也有 4 路超线程作为另一种方式(除了 out-of-order exec)来隐藏其 SIMD 指令的 high-ish 延迟。例如,FMA/add/sub 延迟在 KNL 上是 6 个周期,而在 Skylake/Coffee Lake 上是 4 个周期。

因此,将问题分解为多个线程有时可以显着提高 KNL 上每个单独内核的利用率。但在主流 big-core CPU 与 Coffee Lake 一样,其巨大的 out-of-order 执行能力已经可以在许多循环中找到并利用所有 instruction-level 并行性,即使循环体对每个独立输入执行一系列操作。