混合使用EVEX和VEX编码方案的惩罚是什么?

10

这是一个已知问题,混合使用VEX编码指令和非VEX指令会有惩罚,程序员必须意识到这一点。

有一些类似这样的问题和答案。解决方案取决于您的编程方式(通常在转换后应该使用zeroupper)。但我的问题是关于EVEX编码方案的。由于没有像_mm512_zeroupper()这样的内部函数,因此在使用VEX编码和EVEX编码指令时似乎没有惩罚。但是,EVEX为4字节,而VEX为3字节,向量长度分别为512位和256位。

由于AVX-512对我来说不可用,我想问一下是否需要注意什么,当我们想要混合使用它们时。


1
罚款是由于部分寄存器更新而不是不同的编码方案。 AVX 的设计更加小心,因此不会出现相同的问题。 - fuz
@fuz,你的意思是管道被清空,每个指令都应该有新的编码吗?我不理解部分寄存器更新... - Amiri
如果您使用非VEX指令向一个xmm寄存器写入数据,而这个xmm寄存器的高位部分不为零,则结果必须与其上半部分合并。传统编码的SSE指令只能更新每个xmm寄存器的下半部分(就像16位指令会保留相应寄存器的高48位一样)。 vzeroupper通过清除每个向量寄存器的上半部分解决了这个问题。 - fuz
1个回答

9

没有任何惩罚可以混合使用VEX 128/256或EVEX 128/256/512在当前的CPU上,并且未来的CPU也不会有任何惩罚。

所有的VEX和EVEX编码指令都被定义为将目标向量寄存器的高字节清零,直到CPU支持的最大向量宽度。这使得它们对于任何未来更宽的向量都是具备未来性的,而不需要像vzeroupper那样的丑陋操作。


相关的减速问题:请参见@BeeOnRope的评论,关于写入完整的512位寄存器会产生永久影响直到在SKX上使用vzeroupper,如果您明确地写入ZMM寄存器(而不是通过相应的YMM或XMM寄存器的隐式零扩展)。这使得每个较窄的向量指令表现得像Turbo频率限制下的512位指令一样。
没有错误依赖或额外的时钟周期,只是每个时钟周期不像完全Turbo那样短。端口1未关闭:我们仍然有每个时钟3个vpaddd xmm/ymm
这是一个“全局”的核心状态:一个受污染的zmm0..15寄存器会影响整个核心,只有vzeroupper/all才能恢复更高的turbo。(但据报道,对zmm16..31的写入不是问题)。仅使用正常的零扩展XMM YMM VEX或EVEX指令写入受影响的ZMM寄存器的低半部分无法使您摆脱该“模式”/状态。即使是像VEX vpxor或EVEX vpxord这样的清零习惯用法也不能帮助解决受污染的寄存器问题。实际上,vpxord zmm0,zmm0,zmm0可能会导致问题,这在清零习惯用法中很奇怪。
用户Mysticial和BeeOnRope进行的两个不同实验表明,SKX的物理寄存器文件具有512位条目;依赖于矢量PRF大小以查找ILP的微基准测试发现,“大约为150到158的SIMD推测PRF大小”,256位或512位向量相同。 (根据Intel针对Skylake-client的公布信息和实验结果,我们知道256位PRF大小大约正确。)因此,我们可以排除存储架构ZMM寄存器需要2个PRF条目和两倍读/写端口的模式。

我目前的解释猜测是,也许有一个距调度程序更远的upper256 PRF,或者只是额外的宽度共享主矢量PRF中的相同索引。如果存在这样的情况,光速传播延迟可能会限制最大睿频当upper256 PRF被启用时。这种硬件设计假设无法通过软件进行测试,但与仅使用vzeroupper / vzeroall退出错误状态(如果我正确,那么让PRF的upper256部分关闭,因为该指令使我们知道它未使用)兼容。我不确定zmm16..31为什么不重要。

CPU会跟踪是否有任何上256位部分为非零,因此如果可能的话,xsaveopt可以使用更紧凑的块。在中断处理程序中与内核的xsaveopt / restore交互是可能的,但我大多数情况下提到这一点只是作为另一个原因,为什么CPU会跟踪这一点。 请注意,这个ZMM dirty-upper问题不是由于混合VEX和EVEX造成的。如果您对所有128位和256位指令都使用EVEX编码,那么您将遇到相同的问题。该问题源于在第一代AVX512 CPU上混合512位和较窄向量,其中512位有点过长,它们更适用于较短的向量。(端口1关闭以及端口5 FMA的更高延迟)。
我想知道这是否是有意为之,还是一个设计缺陷。

在AVX512代码中尽可能使用VEX是一件好事。

VEX相对于EVEX可以节省代码大小。有时,在解包或在元素宽度之间转换时,您可能会得到更窄的向量。

(即使考虑到将512位与较短的向量混合的上述问题,128/256位指令也不劣于它们的512位等效物。它们在不应该降低最大睿频时保持最大睿频,但这就是全部。)

使用 VEX 编码的 vpxor xmm0,xmm0,xmm0 已经是将 ZMM 寄存器清零的最高效方式,相比于vpxord zmm0,zmm0,zmm0 节省了 2 个字节,并且运行速度至少与后者一样快。MSVC 已经使用这种方式一段时间了,而 clang 6.0(trunk)在我报告了优化问题之后也开始使用。 (gcc vs. clang on godbolt 除了代码大小之外,它在将512b指令拆分为两个256b操作的未来CPU上可能更快。 (请参见Agner Fog在Is vxorps-zeroing on AMD Jaguar/Bulldozer/Zen faster with xmm registers than ymm?中的回答)。
同样,水平求和应该缩小到256b,然后是128b作为第一步,这样它们可以使用更短的VEX指令,并且在某些CPU上,128b指令的uops较少。同样,在通道内混洗通常比通道交叉更快。

SSE/AVX为何成为问题的背景

请参考Agner Fog在Intel论坛上2008年的帖子以及其余评论AVX设计的帖子。他正确地指出,如果英特尔在首次设计SSE时计划扩展到更宽的向量,并提供一种无论宽度如何都可以保存/恢复完整向量的方法,这个问题就不会存在。

另外有趣的是,Agner在2013年对AVX512的评论以及在Intel论坛上引发的讨论:AVX-512是一个重大进步,但重复了过去的错误!


当AVX首次引入时,他们本可以定义遗留的SSE指令行为为将上部通道清零,这样就可以避免需要使用vzeroupper和保存上部状态(或虚假依赖)。
调用约定只需允许函数破坏向量寄存器的上部通道(就像当前调用约定已经做到的那样)。
问题在于内核中非AVX感知代码异步破坏了上部通道。操作系统已经需要是AVX感知的来保存/恢复完整的向量状态,而AVX指令会出现错误 如果操作系统没有在MSR中设置一个位以承诺此支持。因此,您需要一个AVX感知的内核才能使用AVX,那么问题出在哪里呢?
问题基本上在于二进制Windows设备驱动程序,它们手动使用遗留的SSE指令“手动”保存/恢复一些XMM寄存器。如果这样做会隐式地清零,这将破坏用户空间的AVX状态。
AVX不安全启用Windows系统使用这些驱动程序,因此英特尔设计了AVX,使传统的SSE版本保留未修改的上部通道。让非AVX感知的SSE代码有效运行需要某种惩罚。
我们要感谢Microsoft Windows的二进制软件分发,因为它迫使英特尔决定施加SSE/AVX转换惩罚之痛。
Linux内核代码必须在代码向量寄存器周围调用kernel_fpu_begin/kernel_fpu_end,这会触发常规的保存/恢复代码,该代码必须了解AVX或AVX512。因此,任何构建AVX支持的内核都将支持每个想要使用SSE或AVX的驱动程序/模块(例如RAID5/RAID6),即使是非AVX感知的二进制内核模块(假设它被正确编写,而不是自己保存/恢复几个xmm或ymm寄存器)。

Windows有类似的未来证明的保存/恢复机制,KeSaveExtendedProcessorState,这使您可以在内核代码中使用SSE / AVX代码(但不能用于中断处理程序)。我不知道为什么驱动程序没有始终使用它;也许它很慢或者一开始不存在。如果它已经可用了足够长的时间,那么这纯粹是二进制驱动程序编写者/分发者的责任,而不是微软自己。

(对于OS X,我不确定。如果二进制驱动程序手动保存/恢复xmm寄存器而不是告诉操作系统下一个上下文切换需要还原FP状态以及整数,那么他们也是问题的一部分。)


1
我在另一个答案中有解释:https://dev59.com/jVgR5IYBdhLWcg3wC5mn - Yuhong Bao
关于“在AVX512代码中尽可能使用VEX是一件好事”的建议同样适用于SSE,如果您在AVX512中坚持使用寄存器16..31。SSE的编码比VEX更小,因此这实际上是最优选择。 - Noah
@Noah:通常只针对SSE1;SSE2/SSE3通常大小相同,因为它可以使用2字节VEX(这在更多情况下比避免使用非VEX编码中的REX前缀可能)。而SSSE3及以上需要更多强制前缀,但也需要3字节VEX,所以通常大小相同。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接