从Agner Fog's "Optimizing software in C++"开始:
在某些英特尔处理器上混合使用AVX支持和不使用AVX支持编译的代码时会出现问题。由于YMM寄存器状态的变化,从AVX代码转换为非AVX代码时会导致性能损失。在从AVX代码转换为非AVX代码之前,应通过调用内部函数_mm256_zeroupper()来避免这种损失。在以下情况下可能需要这样做:
·如果程序的一部分是使用AVX支持编译的,而程序的另一部分是在不使用AVX支持的情况下编译的,则在离开AVX部分之前调用_mm256_zeroupper()。
·如果使用CPU调度在多个版本中编译函数,无论是否使用AVX,请在离开AVX部分之前调用_mm256_zeroupper()。
·如果使用AVX支持编译的一段代码调用编译器自带库以外的库中的函数,并且该库不支持AVX,则在调用库函数之前调用_mm256_zeroupper()。
我想知道什么是 * 一些英特尔处理器 *。具体来说,有没有处理器在过去五年。所以我知道是否为时已晚,以修复丢失的_mm256_zeroupper()
调用或没有。
2条答案
按热度按时间sr4lhrrt1#
TL:DR:不要手动使用
_mm256_zeroupper()
intrinsic,编译器理解SSE/AVX转换的内容,并在需要的地方发出vzeroupper
。(包括自动矢量化或扩展memcpy/memset/YMM regs时)“部分英特尔处理器”是指除至强融核之外的所有处理器。
至强融核(KNL / KNM)没有针对运行传统SSE指令而优化的状态,因为它们纯粹是为运行AVX-512而设计的。传统SSE指令可能总是有错误的依赖项合并到目标中。
在带有AVX或更高版本的主流CPU上,有两种不同的机制:节省肮脏的鞋面(SnB通过Haswell和Ice Lake)或虚假依赖(Skylake)。请参阅Why is this SSE code 6 times slower without VZEROUPPER on Skylake?两种不同风格的SSE/AVX惩罚
有关asm
vzeroupper
(在编译器生成的机器码中)影响的相关问答:C或C++源代码中的内部函数
在C/C++源代码中绝对不要使用
_mm256_zeroupper()
。我们已经决定让编译器在需要的地方自动插入vzeroupper
指令,这几乎是编译器能够优化包含intrinsic的函数并仍然可靠地避免转换损失的唯一明智的方法。所有主流编译器都可以自动向量化和/或使用YMM寄存器内联memcpy/memset/array init,因此需要跟踪在此之后使用vzeroupper
的情况。约定是在调用或返回时使CPU处于clean-uppers状态,除非调用按值接受
__m256
/__m256i/d
参数的函数(在寄存器中或根本没有),或返回这样的值时。目标函数(被调用方或调用方)本质上必须是AVX感知的,并且预期脏上状态,因为满的YMM寄存器处于-用作调用约定的一部分。x86-64 System V在向量寄存器中传递向量。Windows
vectorcall
也是这样,但最初的Windows x64约定(现在命名为“fastcall”以区别于“vectorcall”)通过隐藏指针在内存中按值传递向量。(这通过使每个arg始终适合8字节槽来优化可变变量函数。)IDK编译Windows非vectorcall调用的编译器如何处理这个问题,他们是否假设函数可能会查看它的args,或者至少在某些时候仍然负责使用vzeroupper
,即使它不这样做。可能是的,但是如果您正在编写自己的代码生成后端,或者手写的asm,如果这种情况与您相关,请查看您关心的一些编译器实际上是如何做的。一些编译器在从一个带向量参数的函数返回之前也会省略
vzeroupper
,因为调用者显然是支持AVX的。而且关键的是,显然编译器不应该期望调用像void foo(__m256i)
这样的函数会让CPU处于clean-upper状态,所以被调用者在这样的函数之后、call printf
之前或其他地方仍然需要一个vzeroupper
。编译器具有控制
vzeroupper
使用的选项例如,GCC
-mno-vzeroupper
/clang-mllvm -x86-use-vzeroupper=0
(默认值为-mvzeroupper
,用于执行上述操作,在需要时使用)。-march=knl
(Knight 's Landing)暗示了这一点,因为它在至强融核CPU上不需要,而且速度非常慢(因此应该积极避免)。或者如果你用
-mavx -mno-veroupper
构建libc(和你使用的任何其他库),你可能需要它。glibc有一些手写的asm函数,比如strlen,但是大多数都有AVX 2版本。所以只要你不是在一个只有AVX 1的CPU上,字符串函数的传统SSE版本可能根本就用不到。对于MSVC,当编译使用AVX内部函数的代码时,你肯定会更喜欢使用
-arch:AVX
。我认为如果你混合了__m128
和__m256
而没有/arch:AVX
,MSVC的一些版本可能会生成导致转换损失的代码。但是要注意,该选项甚至会使像_mm_add_ps
这样的128位内部函数使用AVX编码(vaddps
)而不是传统SSE(addps
),并允许编译器使用AVX自动矢量化。有一个未公开的开关/d2vzeroupper
可启用自动vzeroupper
生成(默认),/d2vzeroupper-
禁用它-请参见What is the /d2vzeroupper MSVC compiler optimization flag doing?MSVC和GCC/clang可能被诱骗执行传统SSE编码,该编码写入带有脏上限的XMM寄存器:
编译器启发式可能会假设有一个VEX编码可用于一个函数中的任何指令,该函数肯定(无条件地)已经执行了AVX指令。有些,如
cvtpi2ps xmm, mm
(MMX+SSE)或movqd2d xmm, mm
(SSE 2)没有VEX格式,也没有_mm_sha1rnds4_epu32
-它是在Silvermont-family上首次引入的,该fix直到Gracemont(桤木Lake)才支持AVX,因此它是以128位非VEX编码引入的,但仍然没有VEX编码。(This使用SHA或
cvtpi2ps
并不明智,只是随机使用vpaddb
强制执行一些额外的寄存器复制。)一个月一次
MSVC和GCC基本相同(尽管GCC在这种情况下优化了MMX寄存器的使用,使用
vcvtdq2ps
/vshufps
,但这种情况可能不会经常发生)。这些是编译器错误,应该在编译器中修复,但是如果需要,您可以在特定情况下使用
_mm256_vzeroupper()
解决这些错误。通常情况下,编译器启发式工作良好;例如,如果函数中后面的代码可能有条件地运行常规指令(如
paddb
)的传统SSE编码,则if(a) _mm256...
的asm块将以vzeroupper
结束。(这仅在MSVC中可能; gcc/clang要求使用__attribute__((target("avx")))
或"avx2"
编译包含AVX 1/ 2指令的函数,这使得它们可以在函数中的任何地方使用vpaddb
作为_mm_add_epi8
。您必须在每个函数级别上基于CPU特性进行分支/分派,这是有意义的,因为通常您希望使用AVX运行整个循环。)kgqe7b3p2#
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中无效