也可以看看首次使用 AVX 256 位向量会减慢 128 位向量和 AVX 标量运算 https://stackoverflow.com/questions/66874161/first-use-of-avx-256-bit-vectors-slows-down-128-bit-vector-and-avx-scalar-ops回复:如果任何上层脏了,则将 128 位 AVX 操作隐式扩展到 256 位。 (包括为了“轻型”与“重型”涡轮限制 https://stackoverflow.com/questions/56852812/simd-instructions-lowering-cpu-frequency)。这可能是使用的一个原因vzeroupper
,特别是如果程序的某些区域使用 256 位向量(尤其是“轻型”指令,例如除乘法之外的整数内容),而其他区域则大量使用 128 位 FMA。没有vzeroupper
,128 位 FP 数学指令可能会降低您的最大睿频,就像您一直在使用繁重的 256 位指令一样。 (如果你无论如何都这样做,也许没什么大不了的)。
你是对的,如果你的整个程序不使用any写入的非 VEX 指令xmm
寄存器,你不需要vzeroupper
以避免状态转换惩罚。
请注意,非 VEX 指令可能潜伏在 CRT 启动代码和/或动态链接器或其他非常不明显的地方。
也就是说,非 VEX 指令在运行时只能造成一次性惩罚。反之则不然:一条 VEX-256 指令通常可以生成非 VEX 指令(或仅使用该寄存器)程序的其余部分很慢 https://stackoverflow.com/questions/41303780/why-is-this-sse-code-6-times-slower-without-vzeroupper-on-skylake/41349852#41349852.
There's 混合 VEX 和 EVEX 时没有惩罚 https://stackoverflow.com/questions/46080327/what-is-the-penalty-of-mixing-evex-and-vex-encoded-scheme,所以不需要使用vzeroupper
there.
在 Skylake-AVX512 上:vzeroupper
or vzeroall
是在弄脏 ZMM 寄存器后恢复 max-turbo 的唯一方法,假设您的程序仍在 xmm/ymm0..15 上使用任何 SSE*、AVX1 或 AVX2 指令。
也可以看看Skylake 是否需要 vzeroupper 来让 Turbo 时钟在仅读取 ZMM 寄存器、写入 k 掩码的 512 位指令后恢复? https://stackoverflow.com/questions/58568514/does-skylake-need-vzeroupper-for-turbo-clocks-to-recover-after-a-512-bit-instruc- 仅仅读取 zmm 不会导致这种情况。
发表者聊天中@BeeOnRope https://chat.stackoverflow.com/transcript/message/43768745#43768745:
AVX-512 指令对周围代码有一个新的、非常糟糕的影响:一旦执行 512 位指令(可能除了不写入 zmm 寄存器的指令),核心就会进入“上 256 脏状态” 。在此状态下,任何后续标量 FP/SSE/AVX 指令(任何使用 xmm 或 ymm 寄存器的指令)都将在内部扩展至 512 位。这意味着处理器将被锁定到不高于 AVX Turbo(所谓的“L1 许可证”),直到发布 vzeroupper 或 vzeroall 为止。
与早期 AVX 和旧版非 VEX SSE(仍然存在于 Skylake Xeon 上)的“脏上 128”问题不同,这会由于频率较低而减慢所有代码的速度,但不会出现“合并 uops”或错误依赖关系或类似的事情:只是较小的操作被有效地视为 512 位宽,以实现零扩展行为。
关于“写下半部分......” -不,它是一个全局状态,并且只有 vzero让你摆脱困境*。即使您弄脏了 zmm 寄存器但对 ymm 和 xmm 使用了不同的寄存器,也会发生这种情况。即使唯一的脏指令是像这样的归零惯用法,也会发生这种情况vpxord zmm0, zmm0, zmm0
. 但写入 zmm16-31 时不会发生这种情况.
他的描述actually将所有向量运算扩展到 512 位并不完全正确,因为他后来证实这不会降低 128 和 256 位指令的吞吐量。但我们知道,当 512 位微指令运行时,端口 1 上的矢量 ALU 会关闭。 (因此,通常可通过端口 0 和 1 访问的 256 位 FMA 单元可以组合成一个 512 位单元,用于所有 FP 数学、整数乘法以及可能的其他一些内容。某些 SKX Xeon 在端口上有第二个 512 位 FMA 单元5、有些则不然。)
仅使用 AVX1 / AVX2 后的最大涡轮增压(包括像 Haswell 这样的早期 CPU):如果一段时间没有使用执行单元的上半部分,则机会性地关闭它们(有时允许更高的 Turbo 时钟速度)取决于最近是否使用过 YMM 指令,而不是取决于是否使用过 YMM 指令。上半部分是否脏。所以据我所知,vzeroupper
does not对于 256 位最大睿频较低的 CPU,在使用 AVX1 / AVX2 后帮助 CPU 更快地取消时钟速度。
这与英特尔的 Skylake-AVX512 (SKX / Skylake-SP) 不同,其中 AVX512 有点“固定”。
VZEROUPPER
可能会进行上下文切换slightly cheaper
因为CPU仍然知道ymm-upper状态是干净的还是脏的。
如果干净的话我觉得xsaveopt https://github.com/HJLebbink/asm-dude/wiki/XSAVEOPT or xsavec
可以更紧凑地写出 FPU 状态,而无需存储全零的上半部分(只需设置一点表明它们是干净的)。注意在 SSE/AVX 的状态转换图中 https://stackoverflow.com/questions/41303780/why-is-this-sse-code-6-times-slower-without-vzeroupper-on-skylake/41349852#41349852 that xsave
/ xrstor
是图片的一部分。
一个额外的vzeroupper
仅当您的代码不使用任何 256b 指令时才值得考虑long在此之后的时间,因为理想情况下,在下次使用 256 位向量之前,您不会进行任何上下文切换/CPU 迁移。
这在 AVX512 CPU 上可能不太适用:vzeroupper
/ vzeroall https://www.felixcloutier.com/x86/vzeroall#vzeroall--vex-256-encoded-version-不要碰ZMM16..31,只碰ZMM0..15。所以之后你仍然可以有很多脏状态vzeroall
.
(理论上合理):脏的上半部分可能会占用物理寄存器(尽管 IDK 没有任何证据证明这在任何真实的 CPU 上都是正确的)。如果是这样,它将限制 CPU 寻找指令级并行性的无序窗口大小。 (ROB 大小是另一个主要限制因素,但 PRF 大小可能是瓶颈 http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/.)
这在 Zen2 之前的 AMD CPU 上可能是正确的,其中 256b 操作被分成两个 128b 操作。 YMM 寄存器在内部作为两个 128 位寄存器进行处理,例如vmovaps ymm0, ymm1
以零延迟重命名低 128,但上半部分需要一个微指令。 (看阿格纳·福格的微建筑 pdf http://agner.org/optimize/)。未知是否vzeroupper
不过,实际上可以放弃上半部分的重命名。 AMD Zen 上的清零习惯(与 SnB 系列不同)仍然需要后端 uop 来写入寄存器值,即使对于 128b 低半部分也是如此;只有 mov-elimination 可以避免后端 uop。因此可能不存在可以重命名上层的物理零寄存器。
实验中ROB尺寸/PRF尺寸博客文章 http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/不过,表明 Sandybridge 中的 FP 物理寄存器文件条目是 256 位。vzeroupper
不应该在带有 AVX/AVX2 的主流 Intel CPU 上释放更多寄存器。 Haswell 式的转换惩罚足够慢,以至于它可能会耗尽 ROB 来保存或恢复上部到未重命名的单独存储,而不是使用有价值的 PRF 条目。
Silvermont 不支持 AVX。它使用单独的退休登记档案 https://www.realworldtech.com/silvermont/4/对于架构状态,因此无序 PRF 仅保存推测的执行结果。因此,即使它确实支持 128 位半部的 AVX,上半部脏的陈旧 YMM 寄存器也可能不会占用重命名寄存器文件中的额外空间。
KNL(Knight's Landing / Xeon Phi)是专门为运行 AVX512 而设计的,因此推测其 FP 寄存器文件具有 512 位条目。它基于 Silvermont,但核心的 SIMD 部分不同(例如,根据 Agner Fog 的说法,它可以重新排序 FP/向量指令,而 Silvermont 只能推测性地执行它们,但不能在 FP/向量管道内重新排序)。尽管如此,KNL 也可能使用单独的退休寄存器文件,因此即使 ZMM 能够分割 512 位条目来存储两个 256 位向量,脏 ZMM 上层也不会消耗额外的空间。这不太可能,因为 KNL 上仅 AVX1/AVX2 的较大乱序窗口不值得花费晶体管。
vzeroupper
在 KNL 上比主流 Intel CPU 慢得多(64 位模式下每 36 个周期一个),所以你可能不想使用,尤其是为了微小的上下文切换优势。
在 Skylake-AVX512 上,证据支持矢量物理寄存器文件为 512 位宽的结论。
某些未来的 CPU 可能会将物理寄存器文件中的条目配对来存储宽向量,即使它们通常不会像 AMD 对 256 位向量那样解码为单独的微指令。
@神秘报道 https://stackoverflow.com/questions/48892733/can-avx2-compiled-program-still-use-32-registers-of-an-avx-512-capable-cpu#comment84791900_48892842使用 YMM 与 ZMM 的长 FP 依赖链的代码会意外变慢,但其他方面的代码相同,但后来的实验不同意以下结论:当高 256 位脏时,SKX 对 ZMM 寄存器使用 2x 256 位寄存器文件条目。
Ice Lake/Sapphire Rapids 上的 AVX-512 和物理寄存器
https://chipsandcheese.com/2023/01/15/golden-coves-vector-register-file-checking-with-official-spr-data/ https://chipsandcheese.com/2023/01/15/golden-coves-vector-register-file-checking-with-official-spr-data/
[...]
虽然在服务器 Ice Lake 上进行的测试表明英特尔的机制远没有那么复杂。相反,核心只是记住上一组 ZMM 寄存器是否正在使用。如果您使用 AVX-512 引入的任何额外寄存器(即 ZMM16 到 31),Ice Lake 会保留另外 16 个寄存器来保存已知良好状态。无论你触摸其中一个还是全部,都没有关系。 Golden Cove 是 Ice Lake 的继承者,可以使用类似的机制。
...
因此,Zen 4 没有采用与 Ice Lake 相同的寄存器保存优化。
但不幸的是,我不认为vzeroupper
/ vzeroall
可以帮忙解决这个问题;它不会影响 ZMM16..31,因此无法将它们恢复到“干净”状态并释放那些额外的 16 个物理寄存器以用于乱序执行。
如果我理解正确的话,手动将它们异或归零will阻止他们使用物理寄存器(vpxord xmm16, xmm16, xmm16
通过xmm31);要么有一个额外的位来指示全零,要么有一个物理零寄存器,重命名器可以将它们指向。但可能仍然有 16 个额外的 PRF 条目保留用于退休状态,即使实际的 RAT 条目没有指向它们。
随着他们归零,xsave
/xrstor
上下文切换时可能会回到 zmm16-31-unused 状态。 CPU 大概必须能够以某种方式返回到该状态,而不是冷启动或进入深度睡眠状态。
该文章还有一些其他有趣的发现,例如 320 个向量 PRF 条目中只有 220 个能够保存 512 位结果。因此,只要足够,就使用 256 位指令(例如,从缩小到 256 位开始进行水平缩减)可以帮助乱序执行器看得更远。