使用AVX CPU指令:没有“/ arch:AVX”
我的C ++代码使用SSE,现在我想改进它以支持AVX。 所以我检测AVX何时可用,并调用一个使用AVX命令的函数。 我使用Win7 SP1 + VS2010 SP1和一个带有AVX的CPU。
要使用AVX,有必要包含这一点:
#include "immintrin.h"
然后你可以使用像_mm256_mul_ps
函数,如_mm256_mul_ps
, _mm256_add_ps
等。问题是,默认情况下,VS2010产生的代码工作非常缓慢,并显示警告:
警告C4752:find英特尔(R)高级vector扩展; 考虑使用/ arch:AVX
看来VS2010实际上并不使用AVX指令,而是模拟它们。 我添加/arch:AVX
的编译器选项,并取得了良好的效果。 但是这个选项告诉编译器在可能的地方使用AVX命令。 所以我的代码可能会在不支持AVX的CPU上崩溃!
所以问题是如何使VS2010编译器生成AVX代码,但只有当我直接指定AVX内部函数。 对于SSE它可以工作,我只是使用SSE内在函数,它产生的SSE代码没有像/arch:SSE
这样的编译器选项。 但是对于AVX来说,由于某种原因它不起作用。
你所看到的行为是昂贵的状态切换的结果。
见Agner雾的手册的第102页:
optimize/microarchitecture.pdf
每当您在SSE和AVX指令之间来回切换时,您将会付出极高(〜70)的周期处罚。
当你编译没有/arch:AVX
,VS2010会生成SSE指令,但是只要你有AVX内在函数,仍然会使用AVX。 因此,你会得到既有SSE又有AVX指令的代码 – 这些指令会有这些状态切换的惩罚。 (VS2010知道这一点,所以它发出了你所看到的警告。)
因此,你应该使用所有的SSE,或所有的AVX。 指定/arch:AVX
告诉编译器使用所有的AVX。
这听起来像是你想要制作多个代码path:一个是SSE,一个是AVX。 为此,我build议你将SSE和AVX代码分成两个不同的编译单元。 (一个用/arch:AVX
编译,一个不用)然后将它们链接在一起,并使调度员根据正在运行的硬件进行select。
如果您需要混合使用SSE和AVX,请确保正确使用_mm256_zeroupper()
或_mm256_zeroall()
以避免状态切换损失。
TL;博士
使用_mm256_zeroupper();
或_mm256_zeroall();
围绕使用AVX的代码部分(取决于函数参数之前或之后)。 仅对/arch:AVX
源文件使用选项/arch:AVX
而不是整个项目,以避免打破对传统编码SSE专用代码path的支持。
原因
我认为最好的解释是英特尔文章“避免AVX-SSE转换处罚” ( PDF )。 摘要说明:
程序中的256位英特尔®AVX指令和传统英特尔®SSE指令之间的转换可能会导致性能损失,因为硬件必须保存并恢复YMM寄存器的高128位。
将AVX和SSE代码分离为不同的编译单元可能无助于在启用了SSE和启用AVX的目标文件中调用代码,因为当AVX指令或程序集与任何(来自Intel纸):
- 128位内部指令
- SSE内联汇编
- 编译为英特尔®SSE的C / C ++浮点代码
- 调用包含上述任何一个的函数或库
这意味着当使用SSE与外部代码链接时甚至可能会受到惩罚。
细节
AVX指令定义了3个处理器状态,其中一个状态是所有的YMM寄存器被分割,允许下半部分被SSE指令使用 。 英特尔文档“ 英特尔®AVX状态转换:将SSE代码迁移到AVX ”提供了以下状态的图表:
处于状态B(AVX-256模式)时,YMM寄存器的所有位都正在使用。 当一个SSE指令被调用时,必须转换到状态C,这是一个惩罚。 所有YMM寄存器的上半部分必须在SSE启动之前保存到内部缓冲区中,即使它们恰好为零。 转换的成本在“Sandy Bridge硬件上的50-80个时钟周期”。 从C – > A也有一个惩罚,如图2所示。
您也可以在Agner Fog的优化指南 (版本2014-08-07更新版)中find第130页第9.12节“在VEX和非VEX模式之间转换”的状态切换损失的详细信息,在Mystical的答案中引用。 根据他的指导,任何到这个状态的转换都需要“在Sandy Bridge上大约70个时钟周期”。 正如英特尔文件所述,这是一个可以避免的过渡惩罚。
parsing度
为避免转换惩罚,您可以删除所有传统的SSE代码,指示编译器将所有SSE指令转换为其128位指令的VEX编码forms(如果编译器有能力),或者将YMM寄存器置于已知的零状态在AVX和SSE代码之间转换。 实质上,为了保持单独的SSE代码path,必须在使用AVX指令的任何代码之后,将所有16个YMM寄存器的高128位(发出VZEROUPPER
指令) VZEROUPPER
。 手动清零这些位会强制转换到状态A,并且避免了昂贵的代价,因为YMM值不需要通过硬件存储在内部缓冲区中。 执行这个指令的内在是_mm256_zeroupper
。 这个内在的描述是非常丰富的:
当在英特尔®高级vector扩展(英特尔®AVX)指令和传统英特尔®补充SIMD扩展指令(英特尔®SSE)之间转换时,此固有特性有助于清除YMM寄存器的高位。 如果在英特尔®高级vector扩展(英特尔®AVX)指令和传统英特尔之间转换之前, 应用程序通过
VZEROUPPER
(对应于该内部指令) 清除所有YMM寄存器的高位 (设为0) ®补充SIMD扩展(英特尔®SSE)指令。
在Visual Studio 2010+中(甚至可能更老), 你可以用immintrin.h来获得这个内在的东西。
请注意,用其他方法清零这些位并不能消除惩罚 – 必须使用VZEROUPPER
或VZEROALL
指令。
英特尔编译器实现的一个自动解决scheme是在每个包含英特尔AVX代码的函数的开始处插入一个VZEROUPPER
,如果没有参数是YMM寄存器或__m256
/ __m256d
/ __m256i
数据types,并且在函数结束时返回值不是YMM寄存器或__m256
/ __m256d
/ __m256i
数据types。
在野外
这个VZEROUPPER
解决scheme被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
然后VLEAVE();
在每个使用AVX指令的内在函数的函数结束时被调用。