Mik*_*ike 49 c++ performance sse visual-studio-2010 avx
我的C++代码使用SSE,现在我想改进它以支持AVX可用时.所以我检测AVX何时可用并调用使用AVX命令的函数.我使用Win7 SP1 + VS2010 SP1和带AVX的CPU.
要使用AVX,必须包含以下内容:
#include "immintrin.h"
Run Code Online (Sandbox Code Playgroud)
然后你可以使用内在的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而言,由于某些原因它不起作用.
Mys*_*ial 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()
适当地避免状态转换惩罚.
cha*_*pjc 20
TL;博士
使用_mm256_zeroupper();
或_mm256_zeroall();
围绕使用AVX的代码部分(在取决于函数参数之前或之后).仅对/arch:AVX
具有AVX的源文件使用选项而不是对整个项目使用选项,以避免破坏对传统编码的仅SSE代码路径的支持.
原因
我认为最好的解释是英特尔文章"避免AVX-SSE过渡处罚"(PDF).摘要说明:
在程序中转换256位英特尔®AVX指令和传统英特尔®SSE指令可能会导致性能下降,因为硬件必须保存并恢复YMM寄存器的高128位.
如果您在启用SSE和启用AVX的目标文件中调用代码之间切换AVX和SSE代码到不同的编译单元可能无济于事,因为当AVX指令或汇编与任何(来自英特尔)混合时,可能会发生转换纸):
这意味着使用SSE 链接外部代码时甚至可能会受到惩罚.
细节
AVX指令定义了3种处理器状态,其中一种状态是所有YMM寄存器被分割的状态,允许下半部分由SSE指令使用.英特尔文档" 英特尔®AVX状态转换:将SSE代码迁移到AVX "提供了以下状态的图表:
当处于状态B(AVX-256模式)时,YMM寄存器的所有位都在使用中.当调用SSE指令时,必须发生到状态C的转换,这是有惩罚的地方.在SSE可以启动之前,所有YMM寄存器的上半部分必须保存到内部缓冲区,即使它们恰好是零.转换的成本是"Sandy Bridge硬件上50-80个时钟周期的顺序".如图2所示,C - > A还有一个惩罚.
您还可以在第130页第9.12节"在VEX和非VEX模式之间转换"的Agner Fog优化指南(版本更新2014-08-07)中找到导致此速度减慢的状态切换惩罚的详细信息,在Mystical的答案中引用.根据他的指南,任何过渡到这个州的过程都需要"在Sandy Bridge上大约70个时钟周期".正如英特尔文件所述,这是一个可以避免的过渡惩罚.
解析度
为了避免转换惩罚,您可以删除所有传统的SSE代码,指示编译器将所有SSE指令转换为其VEX编码形式的128位指令(如果编译器能够),或者将YMM寄存器置于已知的零状态之前在AVX和SSE代码之间转换.本质上,为了维护单独的SSE代码路径,必须在使用AVX指令的任何代码之后将所有16个YMM寄存器的高128位(发出VZEROUPPER
指令)清零.手动归零这些位会强制转换到状态A,并避免昂贵的代价,因为YMM值不需要由硬件存储在内部缓冲区中.执行此指令的内在函数是_mm256_zeroupper
.这种内在的描述非常有用:
在英特尔®高级矢量扩展指令集(英特尔®AVX)指令和传统英特尔®辅助SIMD扩展指令(英特尔®SSE)指令之间进行转换时,此内在函数可用于清除YMM寄存器的高位.有没有如果应用程序清除所有ymm寄存器的高位过渡惩罚(套到"0")通过
VZEROUPPER
,对于该本征相应的指令,英特尔高级矢量扩展之间转变(英特尔AVX)指令和遗留英特尔之前补充SIMD扩展(英特尔®SSE)指令.
在Visual Studio 2010+(可能更旧)中,您可以使用immintrin.h 获得此内在函数.
请注意,使用其他方法将位清零不会消除损失 - 必须使用VZEROUPPER
或VZEROALL
指令.
英特尔编译器实现的一个自动解决方案是,如果没有参数是YMM寄存器或//数据类型,则在包含英特尔AVX代码的每个函数VZEROUPPER
的开头插入一个,如果返回值不是YMM,则在函数末尾插入一个注册或//数据类型.__m256
__m256d
__m256i
__m256
__m256d
__m256i
在野外
VZEROUPPER
FFTW使用此解决方案生成具有SSE和AVX支持的库.见simd-avx.h:
/* 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
Run Code Online (Sandbox Code Playgroud)
然后VLEAVE();
在每个函数结束时使用内在函数调用AVX指令.