在任何当前 CPU 上混合 VEX 128 / 256 或 EVEX 128 / 256 / 512 都不会受到惩罚,也没有理由期望未来的 CPU 会受到任何惩罚。
所有 VEX 和 EVEX 编码指令都定义为将目标向量寄存器的高字节归零,直到 CPU 支持的最大向量宽度。这使得它们可以适应未来任何更广泛的向量,而不需要像vzeroupper
.
(但是,有一个相关的减速:请参阅@BeeOnRope 的评论vzeroupper
,如果您显式编写 ZMM 寄存器(而不是通过相应 YMM 或 XMM 寄存器的隐式零扩展) ,则在 SKX 上写入具有永久影响的完整 512 位寄存器的评论). 它使每个较窄的向量指令都像 Turbo 频率限制的 512 位指令一样。
没有错误的依赖关系或额外的时钟周期,只是每个时钟周期不如全涡轮机那么短。端口 1没有关闭:我们仍然有 3-per-clock vpaddd xmm/ymm
。
这是一个“全局”内核范围的状态:一个受污染的 zmm0..15 寄存器将损害整个内核,并且只会vzeroupper/all
恢复更高的 turbo。(但据报道写入 zmm16..31 不是问题)。简单地用正常的零扩展 XMM YMM VEX 或 EVEX 指令写入受影响的 ZMM 寄存器的低半部分不会让你脱离那个“模式”/状态。即使是像 VEXvpxor
或 EVEX这样的归零习语vpxord
,污染寄存器也无济于事。vpxord zmm0,zmm0,zmm0
实际上可能会导致问题,这对于归零习语来说很奇怪。
用户 Mysticial 和 BeeOnRope 进行的两个不同的实验(见注释)表明 SKX 的物理寄存器文件有 512 位条目;一个依赖向量 PRF 大小来查找 ILP 的微基准测试发现“SIMD 推测 PRF 大小约为 150 到 158”,对于 256 位或 512 位向量也是如此。(我们知道这对于 256 位 PRF 大小是正确的,基于英特尔发布的 Skylake 客户端信息和那里的实验。)所以我们可以排除存储架构 ZMM 寄存器需要 2 个 PRF 条目和两倍数量的模式。读/写端口。
我目前对解释的猜测是,可能有一个 upper256 PRF 在物理上比主向量 PRF 离调度程序更远,或者只是在主向量 PRF 中共享相同索引的额外宽度。当upper256 PRF通电时,光速传播延迟可能会限制最大涡轮增压,如果这是真的的话。这个硬件设计假设不能用软件测试,但它只兼容vzeroupper
/vzeroall
摆脱不良状态(如果我是对的,让 PRF 的上 256 部分断电,因为一条指令让我们知道它没有被使用) . 不过,我不确定为什么 zmm16..31 对此无关紧要。
CPU 确实会跟踪任何高 256 部分是否为非零,因此xsaveopt
如果可能,可以使用更紧凑的块。在中断处理程序中可以与内核的 xsaveopt / restore 进行交互,但大多数情况下,我提到这只是 CPU 会跟踪它的另一个原因。
请注意,这个 ZMM 上脏问题不是由于混合了 VEX 和 EVEX。如果对所有 128 位和 256 位指令使用 EVEX 编码,也会遇到同样的问题。问题在于在第一代 AVX512 CPU 上混合 512 位和更窄的向量,其中 512 位有点牵强,并且它们针对更短的向量进行了更优化。(端口 1 关闭,端口 5 FMA 的延迟更高)。
我想知道这是故意的,还是设计错误。
尽可能在 AVX512 代码中使用 VEX 是一件好事。
与 EVEX 相比,VEX 节省了代码大小。有时在元素宽度之间解包或转换时,您可能会得到更窄的向量。
(即使考虑到上述将 512 位与较短向量混合的问题,128/256 位指令并不比它们的 512 位等效指令差。它们在不应该降低最大 turbo 时保持降低,但仅此而已。)
VEX 编码vpxor xmm0,xmm0,xmm0
已经是将 ZMM 寄存器归零的最有效方式,相比于节省 2 个字节vpxord zmm0,zmm0,zmm0
并且运行速度至少一样快。MSVC 已经这样做了一段时间,而 clang 6.0 (trunk) 在我报告了错过的优化之后也这样做了。(gcc与godbolt上的clang。
即使不考虑代码大小,在未来将 512b 指令拆分为两个 256b 操作的 CPU 上,它也可能更快。(请参阅 Agner Fog 的回答Is vxorps-zeroing on AMD Jaguar/Bulldozer/Zen faster with xmm registers than ymm?)。
同样,作为第一步,水平和应该缩小到 256b,然后是 128b,因此它们可以使用更短的 VEX 指令,而 128b 指令在某些 CPU 上的微指令更少。此外,车道内洗牌通常比车道交叉更快。
SSE/AVX 为何存在问题的背景
另请参阅Agner Fog 2008 年在英特尔论坛上发表的帖子,以及在首次宣布 AVX 设计时评论该主题的其余部分。他正确地指出,如果英特尔在设计 SSE 时首先计划扩展到更宽的向量,并提供一种不管宽度如何都可以保存/恢复完整向量的方法,那么这不会成为问题。
同样有趣的是,Agner 2013 年对 AVX512 的评论,以及英特尔论坛上的讨论:AVX-512 是向前迈出的一大步——但重复过去的错误!
首次引入 AVX 时,他们可以将传统 SSE 指令的行为定义为将上行通道归零,这将避免需要vzeroupper
并具有保存的上行状态(或错误依赖项)。
调用约定只会允许函数破坏向量 reg 的上层通道(就像当前的调用约定已经做的那样)。
问题是内核中的非 AVX 感知代码异步破坏了上行通道。操作系统已经需要 AVX 感知来保存/恢复完整的向量状态,如果操作系统没有在 MSR 中设置一个承诺这种支持的位,则AVX 指令会出错。所以你需要一个支持 AVX 的内核来使用 AVX,那么有什么问题呢?
问题基本上是遗留的纯二进制 Windows 设备驱动程序,它们使用遗留的 SSE 指令“手动”保存/恢复一些 XMM 寄存器。如果这样做隐式归零,这将破坏用户空间的 AVX 状态。
英特尔没有使 AVX 在使用此类驱动程序的 Windows 系统上不安全启用,而是设计了 AVX,因此旧版 SSE 版本未修改上游通道。让非 AVX 感知 SSE 代码有效运行需要某种惩罚。
我们为 Microsoft Windows 提供了纯二进制软件分发,以感谢英特尔决定造成 SSE/AVX 转换惩罚的痛苦。
Linux 内核代码必须围绕代码向量 regs 调用kernel_fpu_begin
/ kernel_fpu_end
,这会触发必须了解 AVX 或 AVX512 的常规保存/恢复代码。因此,任何使用 AVX 支持构建的内核都将在每个想要使用 SSE 或 AVX 的驱动程序/模块(例如 RAID5/RAID6)中支持它,甚至是不支持 AVX 的仅二进制内核模块(假设它是正确编写的,而不是保存/恢复几个 xmm 或 ymm regs 本身)。
Windows 具有类似的面向未来的保存/恢复机制,KeSaveExtendedProcessorState
允许您在内核代码中使用 SSE/AVX 代码(但不能使用中断处理程序)。IDK 为什么司机并不总是使用它;也许它很慢或者一开始不存在。如果它可用的时间足够长,那么这纯粹是二进制驱动程序编写者/分发者的错,而不是微软自己的错。
(关于 OS X 的 IDK。如果二进制驱动程序“手动”保存/恢复 xmm regs,而不是告诉操作系统下一个上下文切换需要恢复 FP 状态和整数,那么它们也是问题的一部分。)