使用AVX CPU指令:没有“/ arch:AVX”的性能不佳

时间:2011-10-20 17:40:26

标签: c++ performance visual-studio-2010 sse avx

我的C ++代码使用SSE,现在我想改进它以支持AVX可用时。所以我检测AVX何时可用并调用使用AVX命令的函数。我使用Win7 SP1 + VS2010 SP1和带AVX的CPU。

要使用AVX,必须包括:

#include "immintrin.h"

然后您可以使用内置AVX函数,例如_mm256_mul_ps_mm256_add_ps等。 问题是默认情况下,VS2010生成的代码运行速度非常慢,并显示警告:

  

警告C4752:发现英特尔(R)高级矢量扩展;考虑   使用/ arch:AVX

似乎VS2010实际上不使用AVX指令,而是模仿它们。我将/arch:AVX添加到编译器选项中并获得了良好的结果。但是这个选项告诉编译器尽可能在任何地方使用AVX命令。所以我的代码可能会崩溃在不支持AVX的CPU上!

所以问题是如何使VS2010编译器生成AVX代码,但只有当我直接指定AVX内在函数时。对于SSE它可以工作,我只使用SSE内在函数,它生成SSE代码而没有任何编译器选项,如/arch:SSE。但是对于AVX来说,由于某些原因它不起作用。

2 个答案:

答案 0 :(得分:81)

您看到的行为是昂贵的状态转换的结果。

参见Agner Fog手册第102页:

http://www.agner.org/optimize/microarchitecture.pdf

每次在SSE和AVX指令之间来回切换时,都会产生极高的(~70)周期惩罚。

当您在没有/arch:AVX的情况下进行编译时,VS2010将生成SSE指令,但只要您拥有AVX内在函数,它仍将使用AVX。因此,您将获得具有SSE和AVX指令的代码 - 这将具有那些状态切换惩罚。 (VS2010知道这一点,所以它会发出你所看到的警告。)

因此,您应该使用所有SSE或所有AVX。指定/arch:AVX告诉编译器使用所有AVX。

听起来你正在尝试制作多个代码路径:一个用于SSE,一个用于AVX。 为此,我建议您将SSE和AVX代码分成两个不同的编译单元。 (一个用/arch:AVX编译,一个没有编译)然后将它们链接在一起,让调度员根据它运行的硬件进行选择。

如果您 需要 混合SSE和AVX,请务必正确使用_mm256_zeroupper()_mm256_zeroall()以避免状态转换惩罚。

答案 1 :(得分:18)

<强> TL;博士

使用AVX在代码部分周围使用_mm256_zeroupper();_mm256_zeroall();(取决于函数参数之前或之后)。仅对带有AVX的源文件使用选项/arch:AVX而不是整个项目,以避免破坏对传统编码的仅SSE代码路径的支持。

<强>原因

我认为最好的解释是在英特尔文章"Avoiding AVX-SSE Transition Penalties"PDF)中。抽象陈述:

  

在程序中转换256位英特尔®AVX指令和传统英特尔®SSE指令可能会导致性能下降,因为硬件必须保存并恢复YMM寄存器的高128位。

将AVX和SSE代码分成不同的编译单元可能无法帮助如果您在启用SSE和启用AVX的目标文件中切换调用代码,因为转换可能发生在AVX指令或汇编与任何(来自英特尔论文)混合:

  • 128位内在指令
  • SSE内联汇编
  • 编译为英特尔®SSE
  • 的C / C ++浮点代码
  • 调用包含上述任何
  • 的函数或库

这意味着使用SSE链接外部代码时甚至可能会受到惩罚。

<强>详情

AVX指令定义了3个处理器状态,其中一个状态是所有YMM寄存器被分割的位置,允许SSE instructions使用下半部分。英特尔文档“Intel® AVX State Transitions: Migrating SSE Code to AVX”提供了这些状态的图表:

enter image description here

当处于状态B(AVX-256模式)时,YMM寄存器的所有位都在使用中。当调用SSE指令时,必须发生到状态C的转换,这是有惩罚的地方。在SSE可以启动之前,所有YMM寄存器的上半部分必须保存到内部缓冲区,即使它们恰好是零。转换的成本是“Sandy Bridge硬件上50-80个时钟周期的顺序”。还有一个惩罚来自C - &gt; A,如图2所示。

您还可以在VEX(版本更新2014-08版本)中找到第130页第9.12节“Agner Fog's optimization guide与非VEX模式之间的转换”中导致此速度减慢的状态切换惩罚的详细信息-07),在Mystical's answer中引用。根据他的指南,任何过渡到这个州的过程都需要“在Sandy Bridge上大约70个时钟周期”。正如英特尔文件所述,这是一种可避免的过渡惩罚。

解决

为了避免转换惩罚,您可以删除所有传统的SSE代码,指示编译器将所有SSE指令转换为其VEX编码形式的128位指令(如果编译器能够),或者将YMM寄存器放入已知的在AVX和SSE代码之间转换之前的零状态。实质上,为了维护单独的SSE代码路径,在使用AVX指令的任何代码之后,必须将所有16个YMM寄存器的高128位(发出VZEROUPPER指令)清零。手动归零这些位会强制转换到状态A,并避免昂贵的代价,因为YMM值不需要由硬件存储在内部缓冲区中。执行此指令的内在函数是_mm256_zeroupper。这种内在的描述非常有用:

  

当在英特尔®高级矢量扩展指令(英特尔®AVX)指令和传统英特尔®辅助SIMD扩展指令(英特尔®SSE)指令之间进行转换时,此内在函数可用于清除YMM寄存器的高位。如果应用程序通过VZEROUPPER清除所有YMM寄存器的高位(<设置为'0'),则存在无转换惩罚矢量扩展(英特尔®AVX)指令和传统的英特尔®补充SIMD扩展(英特尔®SSE)指令。

在Visual Studio 2010+中(可能更旧),you get this intrinsic使用immintrin.h。

请注意,使用其他方法将这些位清零不会消除惩罚 - 必须使用VZEROUPPERVZEROALL指令。

英特尔编译器实施的一个自动解决方案是,如果所有参数都不是YMM寄存器或{{1>,则在包含英特尔AVX代码的每个函数的开头处插入VZEROUPPER 如果返回的值不是YMM寄存器或__m256 / __m256d,则/ __m256i / __m256数据类型和函数的 / __m256d数据类型。

野外

FFTW使用此__m256i解决方案生成具有SSE和AVX支持的库。见simd-avx.h

VZEROUPPER

然后在每个函数的末尾调用/* 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 ,使用内在函数进行AVX指令。