试图遵循course on Coursera,我尝试为我的Intel i5-8259U
CPU优化示例C ++代码,我相信它支持AVX2
SIMD指令集。现在,AVX2
每个内核提供16个寄存器(称为YMM0
,YMM1
,...,YMM15
),它们的宽度为256位,这意味着每个寄存器最多可以处理同时有4个双精度浮点数。与标量指令相比,利用AVX2
SIMD指令可以优化我的代码,使其运行速度快4倍。
在链接的课程中,您可以尝试在支持Intel Xeon Phi 7210 (Knights Landing)
的{{1}}处理器上使用相同的代码进行数值积分,该处理器使用512位宽的寄存器。这意味着我们应该期望将双精度运算的速度提高8倍。的确,教师使用的代码获得的优化最高达14倍,几乎是8倍的173%。额外的优化归功于OpenMP。
为了在CPU上运行相同的代码,我唯一更改的是将优化标志传递给Intel编译器:我使用AVX512
而不是-xMIC-AVX512
。我获得的速度仅为2的因数,仅是由于256位寄存器上的SIMD向量化而导致的预期速度的50%左右。将这50%与在Intel Xeon Phi处理器上获得的173%进行比较。
为什么仅从-xCORE-AVX2
移至AVX512
会导致性能急剧下降?当然,这里除了SIMD优化之外还有其他事情。我想念什么?
P.S。您可以在文件夹AVX2
here中找到引用的代码。
答案 0 :(得分:4)
TL:DR: KNL(骑士登陆号)只擅长运行专门为其编译的代码,因此获得了更大的加速率,因为它偶然发现了运行不佳的“通用”代码。
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,则由严格{v3与快速fp-model
)控制。英特尔的编译器默认使用-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指令。)
因此,您的Coffee Lake CPU速度提高了2倍,正是您应该期望的,这是一个简单的问题,纯粹是矢量mul / add / FMA SIMD吞吐量的瓶颈(不是内存/缓存或其他任何问题)其他)。
加速取决于代码的工作方式。如果您在内存或缓存带宽上遇到瓶颈,那么更宽的寄存器只会对更好地利用内存并行性并使它保持饱和有帮助。
并且AVX + AVX2添加了更强大的混洗和混合以及其他很酷的功能,但是对于纯垂直SIMD的简单问题却无济于事。
所以真正的问题是为什么AVX512在KNL上的帮助超过4倍?每个《骑士登陆》中AVX512 SIMD指令的8 double
个元素,从SSE2的2个增加到如果指令吞吐量相同,则预期的4倍加速。假设总指令数与AVX512相同。 (情况并非如此:对于相同的循环展开,随着更大的向量以及其他因素,每个循环开销的向量工作量会增加。)
在不知道要编译的源代码的情况下很难确定。 AVX512添加了一些有助于保存指令的功能,例如广播内存源操作数,而不是要求将单独的广播加载到寄存器中。
如果您的问题涉及任何除法运算,则KNL的全能FP运算法则非常慢,通常应使用AVX512ER approximation instruction (28-bit precision) +牛顿-拉夫森迭代法(FMA + mul)将其加倍,得到接近完整的double
(53位有效数字,包括1个隐含位)。 -xMIC-AVX512
启用AVX512ER,并设置调整选项,以便ICC实际上选择使用它。
(相反,Coffee Lake AVX的256位除法吞吐量在每个周期的两倍内并没有比128位除法吞吐量好,但是如果没有AVX512ER,就没有一种有效的方法来使用Newton-Raphson用于{{1 }}。请参阅Floating point division vs floating point multiplication-Skylake数字适用于您的Coffee Lake。
AVX / AVX512可以避免额外的double
指令来复制寄存器,这对于KNL很有帮助(每条不是mul / add / FMA的指令都会消耗FP吞吐量,因为它具有2个时钟的FMA,但只有2个时钟的最大指令吞吐量)。 (https://agner.org/optimize/)
KNL基于Silvermont低功耗内核(这就是它们将这么多内核装入一个芯片的方式)。
相比之下,Coffee Lake具有更强大的前端和后端执行吞吐量:它的停顿每个时钟FMA / mul / add有2个,但每个时钟总指令吞吐量有4个,因此有空间可以运行一些非FMA指令,而不会影响FMA吞吐量。
KNL专为运行AVX512代码而构建。他们并没有浪费晶体管,从而使它可以高效地运行不是专门为它编译的旧代码(使用movaps
或-xMIC-AVX512
)。
但是Coffee Lake是主流的台式机/笔记本电脑核心,必须快速运行任何过去或将来的二进制文件,包括仅使用指令的“传统” SSE2编码而不是AVX的代码。
写入XMM寄存器的 SSE2指令不修改相应YMM / ZMM寄存器的高位元素。 (XMM reg是完整矢量reg的低128位)。从理论上讲,当在支持较宽向量的CPU上运行旧版SSE2指令时,这将产生错误的依赖关系。 (如Sandybridge-family这样的主流Intel CPU可以通过模式转换避免这种情况,如果不正确使用-march=knl
,则可以使用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寄存器。
(但是,如果存在错误的依赖关系,则循环中的vzeroupper
可能不得不等到上一次迭代中的最后一条写入movaps xmm0, [rdi]
的指令完成。这将击败KNL的适度的-订单执行能力可以使跨循环迭代的独立工作重叠,并隐藏负载+ FP延迟。)
在运行旧版SSE / SSE2指令时,也有可能在KNL上解码停顿:它在具有3个以上前缀(包括xmm0
转义字节)的指令上停顿。因此,例如,任何带有REX前缀的SSSE3或SSE4.x指令访问r8..r15或xmm8..xmm15都会导致5到6个周期的解码停顿。
但是如果省略所有0F
/ -x
选项,您将不会有此选择,因为SSE1 / SSE2 + REX仍然可以。只是(可选的REX)+ 2个其他前缀,用于类似66 0F 58 addpd
这样的指令。
请参阅KNL章中的Agner Fog的微体系结构指南: 16.2指令获取和解码。
OpenMP -如果您正在考虑使用多个线程的OpenMP,显然KNL具有更多的内核。
但是,即使在一个物理内核内,KNL还是有4种方式的超线程(除了无序的exec之外)来隐藏其SIMD指令的高延迟。例如,KNL上的FMA /添加/子延迟为6个周期,而Skylake / Coffee Lake则为4个周期。
因此,将一个问题分解为多个线程有时可以显着提高KNL上每个内核的利用率。但是在主流大核心上 像Coffee Lake这样的CPU,其强大的乱序执行功能已经可以在许多循环中找到并利用所有指令级并行性,即使循环体对每个独立的输入执行了一系列操作。