tl;博士仅适用于旧版本的 MSVC
使用_mm256_zeroupper();
或_mm256_zeroall();
围绕使用 AVX 的代码部分(之前或之后取决于函数参数)。仅对/arch:AVX
带有 AVX 的源文件而不是整个项目使用选项,以避免破坏对旧编码 SSE 代码路径的支持。
在现代 MSVC(和其他主流编译器,GCC/clang/ICC)中,编译器知道何时使用vzeroupper
asm 指令。vzeroupper
内联时 强制使用额外的 s 可能会损害性能。请参阅我是否需要在 2021 年使用 _mm256_zeroupper?
原因
我认为最好的解释是在英特尔文章“避免 AVX-SSE 过渡惩罚”(PDF)中。摘要指出:
在程序中在 256 位英特尔® AVX 指令和旧版英特尔® SSE 指令之间转换可能会导致性能下降,因为硬件必须保存和恢复 YMM 寄存器的高 128 位。
如果您在从启用 SSE 和启用 AVX 的目标文件调用代码之间切换,将 AVX 和 SSE 代码分离到不同的编译单元可能无济于事,因为当 AVX 指令或程序集与任何(来自 Intel纸):
- 128 位内在指令
- SSE 内联汇编
- 编译为英特尔® SSE 的 C/C++ 浮点代码
- 调用包含上述任何内容的函数或库
这意味着使用 SSE与外部代码链接时甚至可能会受到处罚。
细节
AVX 指令定义了 3 种处理器状态,其中一种状态是所有YMM寄存器都被拆分,允许SSE 指令使用下半部分。英特尔文档“英特尔® AVX 状态转换:将 SSE 代码迁移到 AVX ”提供了这些状态的图表:

当处于状态 B(AVX-256 模式)时,YMM 寄存器的所有位都在使用中。当调用 SSE 指令时,必须发生到状态 C 的转换,这就是有惩罚的地方。所有 YMM 寄存器的上半部分必须在 SSE 启动之前保存到内部缓冲区中,即使它们恰好为零。转换的成本是“在 Sandy Bridge 硬件上大约 50-80 个时钟周期”。还有一个从 C -> A 的惩罚,如图 2 所示。
您还可以在Agner Fog 的优化指南(更新于 2014-08-07 的版本)中的第 130 页第 9.12 节“ VEX和非 VEX 模式之间的转换”中找到有关状态切换惩罚的详细信息,在Mystical 的答案中引用. 根据他的指南,任何与此状态的转换都需要“在 Sandy Bridge 上大约 70 个时钟周期”。正如英特尔文档所述,这是一种可以避免的过渡惩罚。
Skylake 有一种不同的脏鞋面机制,它会导致带有脏鞋面的传统 SSE 的错误依赖,而不是一次性惩罚。 为什么在 Skylake 上没有 VZEROUPPER 时,这个 SSE 代码会慢 6 倍?
解析度
为避免转换惩罚,您可以删除所有旧版 SSE 代码,指示编译器将所有 SSE 指令转换为其 VEX 编码形式的 128 位指令(如果编译器有能力),或者在之前将 YMM 寄存器置于已知零状态在 AVX 和 SSE 代码之间转换。本质上,为了维护单独的 SSE 代码路径,您必须在任何使用 AVX 指令的代码之后将所有 16 个 YMM 寄存器(发出指令)的高 128 位清零VZEROUPPER
。手动清零这些位会强制转换到状态 A,并避免代价高昂的代价,因为 YMM 值不需要通过硬件存储在内部缓冲区中。执行此指令的内在函数是_mm256_zeroupper
. 此内在函数的描述非常有用:
在英特尔® 高级矢量扩展(英特尔® AVX)指令和旧版英特尔® 补充 SIMD 扩展(英特尔® SSE)指令之间转换时,此内在函数可用于清除 YMM 寄存器的高位。如果应用程序在英特尔® 高级矢量扩展(英特尔® AVX)指令和旧版英特尔® 之间转换之前通过此内在函数的相应指令清除所有 YMM 寄存器的高位(设置为“0”) ,则不会出现转换损失VZEROUPPER
补充 SIMD 扩展(英特尔® SSE)指令。
在 Visual Studio 2010+(可能更早)中,您可以通过 immintrin.h获得此内在函数。
请注意,使用其他方法将位清零并不能消除惩罚 -必须使用VZEROUPPER
or指令。VZEROALL
英特尔编译器实现的一种自动解决方案是,如果参数都不是 YMM 寄存器或//数据类型,则在每个包含英特尔 AVX 代码的函数的开头插入VZEROUPPER
a,如果返回的值不是 YMM,则在函数的末尾插入注册或//数据类型。__m256
__m256d
__m256i
__m256
__m256d
__m256i
在野外
FFTW 使用此VZEROUPPER
解决方案生成同时支持 SSE 和 AVX 的库。见simd-avx.h:
/* Use VZEROUPPER to avoid the penalty of switching from AVX to SSE.
See Intel Optimization Manual (April 2011, version 248966), Section
11.3 */
#define VLEAVE _mm256_zeroupper
然后在每个VLEAVE();
函数的末尾使用 AVX 指令的内在函数调用。