TL:DR: KNL(Knight's Landing)仅擅长运行专门为其编译的代码,因此由于运行不畅而获得了更大的加速”通用”代码。
Coffee Lake 从 128 位 SSE2 到 256 位 AVX 仅获得 2 倍的加速,同时以最佳方式运行“通用”代码和目标代码。
Coffee Lake 等主流 CPU 是现代编译器中的“通用”调优关注的目标之一,它们总体上没有太多弱点。但 KNL 不是;没有任何选项的ICC不关心KNL
您假设加速的基线是标量。但如果没有像 -march=native 或 -xCORE-AVX2 这样的选项,英特尔的编译器 (ICC) 仍将使用 SSE2 自动矢量化,因为这是 x86-64 的基线。
-xCORE-AVX2 不启用自动矢量化,它只是为自动矢量化提供了更多可以使用的指令。优化级别(包括自动矢量化)由-O0/-O2/-O3 控制,对于 FP,由严格与快速fp-model 控制。 Intel 的编译器默认使用-fp-model fast=1(比fast=2 低一级)进行全面优化,所以它类似于gcc -O3 -ffast-math。
但如果没有额外的选项,它只能使用基线指令集,对于 x86-64 是 SSE2。这仍然比标量好。
SSE2 使用 128 位 XMM 寄存器进行打包双精度数学运算,指令吞吐量与 AVX 相同(在您的 i5 Coffee Lake 上),但每条指令的工作量只有一半。 (而且它没有 FMA,因此编译器无法像使用 AVX+FMA 那样将源中的任何 mul+add 操作收缩到 FMA 指令中)。
因此,对于一个纯矢量 mul/add/FMA SIMD 吞吐量(不是内存/缓存或其他任何东西)的简单问题,您的 Coffee Lake CPU 的 2 倍加速正是您应该期望的否则)。
加速取决于您的代码在做什么。如果您在内存或缓存带宽上遇到瓶颈,那么更宽的寄存器只会有助于更好地利用内存并行性并使其保持饱和。
AVX + AVX2 添加了更强大的 shuffle 和 blends 以及其他很酷的东西,但是对于纯垂直 SIMD 的简单问题没有帮助。
所以真正的问题是 为什么 AVX512 在 KNL 上的帮助超过 4 倍? 在 Knight's Landing 上,每个 AVX512 SIMD 指令有 8 个 double 元素,而 SSE2 是 2 个,将提供预期的加速如果指令吞吐量相同,则为 4 倍。假设总指令数与 AVX512 相同。 (事实并非如此:对于同一个循环展开,每个循环开销的向量工作量会随着更宽的向量以及其他因素而增加。)
如果不知道您正在编译什么源代码,很难确定。 AVX512 添加了一些有助于节省指令的功能,例如广播内存源操作数,而不需要单独的广播加载到寄存器中。
如果您的问题涉及任何除法,KNL 的全精度 FP 除法非常慢,通常应使用 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 for double )。请参阅 Floating point division vs floating point multiplication - Skylake 号码适用于您的 Coffee Lake。
AVX / AVX512 可以避免额外的movaps 指令来复制寄存器,这对 KNL 有很大帮助(每条不是 mul/add/FMA 的指令都会消耗 FP 吞吐量,因为它具有每时钟 2 个 FMA,但最大指令吞吐量仅为每时钟 2 个)。 (https://agner.org/optimize/)
KNL 基于 Silvermont 低功耗内核(这就是它们在一个芯片上安装如此多内核的方式)。
相比之下,Coffee Lake 具有更强大的前端和后端执行吞吐量:它的停顿具有每时钟 2 条 FMA/mul/add,但每时钟 4 条总指令吞吐量,因此有空间运行一些非FMA 指令不会影响 FMA 吞吐量。
在 KNL (Xeon Phi) 上运行 SSE/SSE2 指令会导致其他减速
KNL 专为运行 AVX512 代码而构建。他们没有浪费晶体管,使其高效运行不是专门为它编译的遗留代码(使用-xMIC-AVX512 或-march=knl)。
但您的 Coffee Lake 是主流台式机/笔记本电脑核心,必须快速运行任何过去或未来的二进制文件,包括仅使用“传统”SSE2 指令编码而非 AVX 的代码。
写入 XMM 寄存器的 SSE2 指令不会修改相应 YMM/ZMM 寄存器的高位元素。 (XMM reg 是全向量 reg 的低 128 位)。这在理论上会在支持更宽向量的 CPU 上运行旧版 SSE2 指令时产生错误的依赖关系。 (像 Sandybridge 系列这样的主流 Intel CPU 会通过模式转换来避免这种情况,或者如果您没有正确使用 vzeroupper,则会在 Skylake 实际错误依赖项上避免这种情况。请参阅 Why is this SSE code 6 times slower without VZEROUPPER on Skylake? 以比较这两种策略。
KNL 确实显然有办法避免错误的依赖关系:根据 Agner Fog 的测试 (in his microarch guide),他将其描述为 P6 家族在编写时所做的部分寄存器重命名到像 AL 这样的整数寄存器。当您读取完整寄存器时,您只会获得部分寄存器停顿。如果那是准确的,那么 SSE2 代码应该可以在 KNL 上运行,因为没有 AVX 代码读取 YMM 或 ZMM 寄存器。
(但是如果存在错误的依赖关系,循环中的 movaps xmm0, [rdi] 可能必须等到上一次迭代中写入 xmm0 的最后一条指令完成。这将破坏 KNL 适度的乱序执行能力在循环迭代中重叠独立工作并隐藏负载 + 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。
请参阅 KNL 章节中 Agner Fog 的微架构指南:16.2 指令获取和解码。
OpenMP - 如果您希望 OpenMP 使用多线程,显然 KNL 有更多内核。
但即使在一个物理内核中,KNL 也有 4 路超线程作为另一种方式(除了乱序执行)来隐藏其 SIMD 指令的高延迟。例如,FMA/add/sub 延迟在 KNL 上为 6 个周期,而在 Skylake/Coffee Lake 上为 4 个。
因此,将问题分解为多个线程有时可以显着提高 KNL 上每个单独内核的利用率。但在主流大核上
像 Coffee Lake 这样的 CPU,其海量的乱序执行能力已经可以在许多循环中找到并利用所有指令级并行性,即使循环体对每个独立的输入执行一系列操作。