【问题标题】:Do I need to use _mm256_zeroupper in 2021?我需要在 2021 年使用 _mm256_zeroupper 吗?
【发布时间】:2021-10-14 14:11:36
【问题描述】:

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

在某些 Intel 上混合使用和不使用 AVX 支持编译的代码时会出现问题 处理器。从 AVX 代码转换为非 AVX 代码时会降低性能 因为 YMM 寄存器状态发生了变化。应该通过调用来避免这种惩罚 在从 AVX 代码到非 AVX 代码的任何转换之前的内在函数 _mm256_zeroupper()。在以下情况下可能需要这样做:

• 如果程序的一部分是使用 AVX 支持编译的,而程序的另一部分是 在没有 AVX 支持的情况下编译,然后在离开之前调用 _mm256_zeroupper() AVX 部分。

• 如果一个函数使用 CPU 编译成多个版本,有和没有 AVX 调度然后在离开 AVX 部分之前调用 _mm256_zeroupper()。

• 如果使用 AVX 支持编译的一段代码调用库中的函数,而不是 编译器自带的库,而库不支持AVX,那么 在调用库函数之前调用 _mm256_zeroupper()。

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

【问题讨论】:

    标签: c++ sse simd intrinsics avx


    【解决方案1】:

    AVX -> 不归零的 SSE 惩罚适用于当前处理器。见Intel® 64 and IA-32 Architectures Optimization Reference Manual, June 2021。

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

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

    【讨论】:

      【解决方案2】:

      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?

      【讨论】:

      • 写信给Agner,他回复说他会提到编译器可能会在下次手动更新时自动添加_mm256_zeroupper
      • @AlexGuteniev:希望他实际上会说vzeroupper(asm 指令)是由编译器自动添加的。 _mm256_vzeroupper 是内在的,编译器不能通过转换源代码来工作,它们通过发出 asm 来工作。说 _mm256_vzeroupper() 是自动添加几乎没有意义,只是编译器对 SSE-AVX 转换效果的理解足够好,因此不需要它。
      • 当您在不同的翻译单元之间调用自己的 AVX 矢量化函数时,有充分的理由禁用 vzeroupper 的自动生成。如果一个函数不接受或返回向量,编译器必须假定它期望旧 SSE 状态并生成vzeroupper。在这种情况下,应该禁用自动vzeroupper 并在重要的地方为给定的 TU 手动插入内在函数。您可以为其他 TU 启用它。
      • @PeterCordes,它已更新。你会失望的:编译器可能会也可能不会自动插入 _mm256_zeroupper()。编译器的程序集输出会告诉你它做了什么
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-31
      • 2015-07-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 2021-06-03
      相关资源
      最近更新 更多