TL:DR:不要手动使用_mm256_zeroupper() intrinsic,编译器理解 SSE/AVX 转换的东西并在需要的地方发出 vzeroupper。 (包括使用 YMM regs 自动矢量化或扩展 memcpy/memset/whatever 时。)
“某些 Intel 处理器”是除 Xeon Phi 之外的所有处理器。
至强融核 (KNL / KNM) 没有针对运行传统 SSE 指令进行优化的状态,因为它们纯粹是为运行 AVX-512 而设计的。旧版 SSE 指令可能总是将错误的依赖项合并到目标中。
在带有 AVX 或更高版本的主流 CPU 上,有两种不同的机制:保存脏鞋面(通过 Haswell 和 Ice Lake 的 SnB)或虚假依赖项(Skylake)。见Why is this SSE code 6 times slower without VZEROUPPER on Skylake?两种不同风格的SSE/AVX惩罚
关于 asm vzeroupper 效果的相关问答(在编译器生成的机器代码中):
C 或 C++ 源代码中的内部函数
您几乎不应该在 C/C++ 源代码中使用 _mm256_zeroupper()。事情已经解决,让编译器在可能需要的地方自动插入 vzeroupper 指令,这几乎是编译器能够优化包含内在函数的函数并仍然可靠地避免转换惩罚的唯一明智方法。 (特别是在考虑内联时)。所有主要编译器都可以使用 YMM 寄存器自动矢量化和/或内联 memcpy/memset/array init,因此之后需要跟踪使用 vzeroupper。
约定是在调用或返回时使 CPU 处于清理状态,除非调用按值(在寄存器中或根本没有)采用 __m256 / __m256i/d args 的函数,或者返回这样的值时。目标函数(被调用者或调用者)本质上必须是 AVX 感知的,并且需要一个上脏状态,因为作为调用约定的一部分正在使用完整的 YMM 寄存器。
x86-64 System V 在向量 regs 中传递向量。 Windows vectorcall 也可以,但最初的 Windows x64 约定(现在称为“fastcall”以区别于“vectorcall”)通过隐藏指针在内存中按值传递向量。 (这通过使每个 arg 始终适合 8 字节插槽来优化可变参数函数。)IDK 编译 Windows 非向量调用调用的编译器如何处理这个问题,无论他们假设函数可能查看它的 args 还是至少仍然负责使用vzeroupper 在某个时候即使没有。可能是的,但是如果您正在编写自己的代码生成后端或手写 asm,请查看您关心的某些编译器在这种情况下实际执行的操作。
一些编译器通过在从采用向量 args 的函数返回之前省略 vzeroupper 进行优化,因为显然调用者是 AVX 感知的。至关重要的是,显然编译器不应该期望调用像 void foo(__m256i) 这样的函数会使 CPU 处于清理状态,所以被调用者在这样的函数之后、call printf 或其他任何东西之前仍然需要一个 vzeroupper。
编译器可以选择控制vzeroupper 的使用
例如,GCC -mno-vzeroupper / clang -mllvm -x86-use-vzeroupper=0。 (默认为 -mvzeroupper 来执行上述行为,在可能需要时使用。)
-march=knl(Knight's Landing)暗示了这一点,因为它在 Xeon Phi CPU 上不是必需的并且非常慢(因此应该积极避免)。
或者,如果您使用 -mavx -mno-veroupper 构建 libc(以及您使用的任何其他库),您可能会想要它。 glibc 有一些用于 strlen 等函数的手写 asm,但其中大多数都有 AVX2 版本。因此,只要您不在仅支持 AVX1 的 CPU 上,则可能根本不会使用传统 SSE 版本的字符串函数。
对于 MSVC,在编译使用 AVX 内部函数的代码时,您绝对应该更喜欢使用 -arch:AVX。我认为如果您在没有/arch:AVX 的情况下混合__m128 和__m256,某些版本的MSVC 可能会生成导致转换惩罚的代码。但请注意,该选项甚至会使像 _mm_add_ps 这样的 128 位内在函数使用 AVX 编码 (vaddps) 而不是传统的 SSE (addps),并且会让编译器使用 AVX 自动矢量化。有一个未记录的开关 /d2vzeroupper 来启用自动 vzeroupper 生成(默认),/d2vzeroupper- 禁用它 - 请参阅 What is the /d2vzeroupper MSVC compiler optimization flag doing?