我需要在 2021 年使用 _mm256_zeroupper 吗?

Do I need to use _mm256_zeroupper in 2021?

来自Agner Fog's "Optimizing software in C++"

There is a problem when mixing code compiled with and without AVX support on some Intel processors. There is a performance penalty when going from AVX code to non-AVX code because of a change in the YMM register state. This penalty should be avoided by calling the intrinsic function _mm256_zeroupper() before any transition from AVX code to nonAVX code. This can be necessary in the following cases:

• If part of a program is compiled with AVX support and another part of the program is compiled without AVX support then call _mm256_zeroupper() before leaving the AVX part.

• If a function is compiled in multiple versions with and without AVX using CPU dispatching then call _mm256_zeroupper() before leaving the AVX part.

• If a piece of code compiled with AVX support calls a function in a library other than the library that comes with the compiler, and the library has no AVX support, then call _mm256_zeroupper() before calling the library function.

我想知道一些英特尔处理器是什么。具体来说,是否有过去五年制造的处理器。这样我就知道现在修复丢失的 _mm256_zeroupper() 电话是否为时已晚。

A​​VX -> 没有归零的 SSE 惩罚适用于当前处理器。请参见 Intel® 64 和 IA-32 架构 优化参考手册,2021 年 6 月

然而,在 C/C++ 代码中缺少 _mm256_zeroupper() 不一定是问题。编译器可能会自行插入它。所有编译器都这样做:https://godbolt.org/z/veToerhvG

实验表明,自动 vzeroupper 插入在 VS 2015 中有效,但在 VS 2012 中无效

TL:DR: 不要手动使用 _mm256_zeroupper() intrinsic,编译器理解 SSE/AVX 转换内容并在需要时发出 vzeroupper为你。 (包括使用 YMM regs 自动矢量化或扩展 memcpy/memset/whatever 时。)


“部分 Intel 处理器”是 Xeon Phi 以外的所有处理器。

Xeon Phi (KNL / KNM) 没有针对 运行ning 遗留 SSE 指令优化的状态,因为它们纯粹是为 运行 AVX-512 设计的。旧版 SSE 指令可能总是将错误的依赖项合并到目标中。

在带有 AVX 或更高版本的主流 CPU 上,有两种不同的机制:保存脏鞋面(通过 Haswell 和 Ice Lake 的 SnB)或虚假依赖项(Skylake)。查看两种不同风格的SSE/AVX惩罚

asm的作用相关Q&Avzeroupper(在编译器生成的机器码中):


C 或 C++ 源代码中的内部函数

您几乎不应该在 C/C++ 源代码 中使用 _mm256_zeroupper()。事情已经解决了让编译器在可能需要的地方自动插入一条 vzeroupper 指令,这几乎是编译器能够优化包含内在函数的函数并仍然可靠地避免转换惩罚的唯一明智方法。 (特别是在考虑内联时)。所有主要编译器都可以使用 YMM 寄存器自动向量化 and/or 内联 memcpy/memset/array init,因此需要跟踪之后使用 vzeroupper

惯例是在调用或返回 时让 CPU 处于清理状态,除非调用采用 __m256 / [= 的函数17=] args 按值(在寄存器中或根本没有),或当返回这样的值时。目标函数(被调用者或调用者)本质上必须是 AVX 感知的并且期望脏上层状态,因为作为调用约定的一部分正在使用完整的 YMM 寄存器。

x86-64 系统 V 在向量 regs 中传递向量。 Windows vectorcall 也一样,但是原始的 Windows x64 约定(现在命名为“fastcall”以区别于“vectorcall”)通过隐藏指针在内存中按值传递向量。 (这通过使每个 arg 始终适合一个 8 字节的槽来优化可变参数函数。)IDK 编译器如何编译 Windows 非 vectorcall 调用处理这个,他们是否假设函数可能查看它的 args 或者至少是仍然负责在某些时候使用 vzeroupper 即使它没有。可能是的,但是如果您正在编写自己的代码生成后端或手写 asm,那么如果这种情况与您相关,请查看您关心的一些编译器实际上做了什么。

一些编译器在从采用向量参数的函数返回之前也通过省略 vzeroupper 进行优化,因为显然调用者是 AVX 感知的。至关重要的是,显然编译器不应该期望调用像 void foo(__m256i) 这样的函数会使 CPU 处于干净上层状态,因此被调用者在这样的函数之后仍然需要 vzeroupper,在 call printf 或其他什么之前。


编译器有控制vzeroupper用法的选项

例如,GCC -mno-vzeroupper / -mllvm -x86-use-vzeroupper=0。 (默认为 -mvzeroupper 执行上述行为,在可能需要时使用。)

-march=knl (Knight's Landing) 暗示了这一点,因为它不需要并且在 Xeon Phi CPUs 上非常慢(因此应该积极避免)。

或者如果您使用 -mavx -mno-veroupper 构建 libc(以及您使用的任何其他库),您可能需要它。 glibc 有一些手写的 asm 用于 strlen 等函数,但其​​中大部分都有 AVX2 版本。因此,只要您使用的不是 AVX1-only CPU,旧版 SSE 版本的字符串函数可能根本不会被使用。

对于 MSVC,在编译使用 AVX 内在函数的代码时,您绝对应该更喜欢使用 -arch:AVX。我认为如果您混合 __m128__m256 而没有 /arch:AVX,某些版本的 MSVC 可能会生成导致转换惩罚的代码。但是请注意,该选项甚至会使像 _mm_add_ps 这样的 128 位内在函数使用 AVX 编码 (vaddps) 而不是旧版 SSE (addps),并且会让编译器自动-用 AVX 向量化。有未记录的开关 /d2vzeroupper 启用自动 vzeroupper 生成(默认),/d2vzeroupper- 禁用它 - 参见