Ema*_*ele 13 assembly gcc simd vectorization avx
我正在编写一些AVX代码,我需要从可能未对齐的内存中加载.我目前正在加载4个双打,因此我将使用内部指令_mm256_loadu_pd ; 我写的代码是:
__m256d d1 = _mm256_loadu_pd(vInOut + i*4);
Run Code Online (Sandbox Code Playgroud)
然后,我使用选项进行编译,-O3 -mavx -g然后使用objdump获取汇编代码以及带注释的代码和line(objdump -S -M intel -l avx.obj).
当我查看底层汇编程序代码时,我发现以下内容:
vmovupd xmm0,XMMWORD PTR [rsi+rax*1]
vinsertf128 ymm0,ymm0,XMMWORD PTR [rsi+rax*1+0x10],0x1
Run Code Online (Sandbox Code Playgroud)
我期待看到这个:
vmovupd ymm0,XMMWORD PTR [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)
并充分利用256位寄存器(YMM0),而不是它看起来像海湾合作委员会已决定在128位部分(填写XMM0),然后再次加载另一半vinsertf128.
有人能够解释这个吗?在MSVC VS 2012中
使用单个vmovupd编译等效代码.
我运行gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0在Ubuntu的18.04 X86-64.
Pet*_*des 11
GCC的默认调整(-mtune=generic)包括-mavx256-split-unaligned-load和-mavx256-split-unaligned-store,因为在某些情况下,当内存实际上在运行时未对齐时,它会对某些CPU(例如第一代Sandybridge和某些AMD CPU)进行较小的加速.
使用-O3 -mno-avx256-split-unaligned-load -mno-avx256-split-unaligned-store,如果你不希望这样,或者更好,使用-mtune=haswell. 或者用于-march=native优化您自己的计算机.没有"generic-avx2"调整.(https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html).
英特尔Sandybridge作为单个uop运行256位负载,在加载端口中需要2个周期.(与AMD不同,它将所有256位向量指令解码为2个独立的uop.)Sandybridge在未对齐的256位加载时遇到问题(如果地址在运行时实际上未对齐).我不知道细节,也没有找到关于减速的具体信息.也许是因为它使用了一个带有16字节库的库存缓存?但IvyBridge可以更好地处理256位负载,并且仍然具有存储缓存.
根据GCC邮件列表关于实现该选项的代码的消息(https://gcc.gnu.org/ml/gcc-patches/2011-03/msg01847.html)," 它加速了一些SPEC CPU 2006基准测试高达6%. "(我认为这是Sandybridge,当时唯一存在的Intel AVX CPU.)
但是如果内存在运行时实际上是32字节对齐,那么即使在Sandybridge和大多数AMD CPU 1上,这也是纯粹的缺点.因此,使用此调整选项,您可能会因为未能告诉编译器有关对齐保证而失败.如果您的循环大多数时间都在对齐的内存上运行,那么您最好至少使用编译单元编译-mno-avx256-split-unaligned-load或调整暗示该选项的选项.
软件拆分会一直带来成本.让硬件处理它使得对齐的盒子非常有效(除了Piledriver 1上的存储),未对齐的情况可能比某些CPU上的软件分裂慢.所以这是悲观的方法,如果数据确实可能在运行时确实未对齐,而不是保证在编译时始终保持对齐,那么这是有道理的.例如,你可能有一个大部分时间都使用对齐缓冲区调用的函数,但你仍然希望它适用于使用未对齐缓冲区调用的罕见/小情况.在这种情况下,即使在Sandybridge上,拆分/存储策略也是不合适的.
缓冲区通常是16字节对齐但不是32字节对齐,因为malloc在x86-64上glibc(和newlibstdc ++中)返回16字节对齐的缓冲区(因为alignof(maxalign_t) == 16).对于大缓冲区,指针通常在页面开始后为16个字节,因此对于大于16的对齐,它总是未对齐.请aligned_alloc改用.
请注意,-mavx并-mavx2没有改变调整的所有选项:gcc -O3 -mavx2仍然曲调为所有的CPU,包括不能实际运行AVX2指令的.这非常愚蠢,因为如果调整"平均AVX2 CPU",你应该使用单个未对齐的256位负载.不幸的是,gcc没有选择这样做,-mavx2并不暗示-mno-avx256-split-unaligned-load或任何事情. 有关具有指令集选择影响的功能请求,请参阅https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80568和https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78762调整.
这就是为什么你应该使用-march=native二进制文件供本地使用,或者-march=sandybridge -mtune=haswell制作可以在各种机器上运行的二进制文件,但可能主要运行在具有AVX的新硬件上.(请注意,即使Skylake Pentium/Celeron CPU也没有AVX或BMI2;可能在256位执行单元或寄存器文件的上半部分有任何缺陷的CPU上,它们禁用VEX前缀的解码并将其作为低端出售奔腾).
gcc8.2的调优选项如下.(-march=x暗示-mtune=x). https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html.
我通过编译并查看包含所有隐含选项的完整转储的注释来检查Godbolt编译器资源管理器-O3 -fverbose-asm.我包含了_mm256_loadu/storeu_ps函数和一个可以自动向量化的简单浮点循环,因此我们也可以查看编译器的作用.
使用-mprefer-vector-width=256(gcc8)或-mno-prefer-avx128(gcc7和更早版本)覆盖调整选项-mtune=bdver3,如果需要,可以获得256位自动矢量化,而不是仅使用手动矢量化.
-mtune=generic:两者-mavx256-split-unaligned-load和-store.可以说,随着英特尔Haswell的出现越来越不合适,后来变得越来越普遍,而且我认为近期AMD CPU的不足之处仍然很小.特别是拆分未对齐的负载,AMD调整选项无法启用.-march=sandybridge并且-march=ivybridge:拆分两者.(我想我已经读过IvyBridge改进了对未对齐的256位加载或存储的处理,所以它不太适合数据可能在运行时对齐的情况.)-march=haswell 然后:既没有启用拆分选项.-march=knl:既没有启用拆分选项.(Silvermont/Atom没有AVX)-mtune=intel:既没有启用拆分选项.即使使用gcc8,自动矢量化-mtune=intel -mavx选择到达读/写目标数组的对齐边界,这与gcc8的常规策略只是使用未对齐.(同样,另一个软件处理案例总是有成本而不是让硬件处理特殊情况.)-march=bdver1(推土机):-mavx256-split-unaligned-store但不是负载.它还设置了gcc8等效的gcc7和更早版本-mprefer-avx128(自动矢量化将只使用128位AVX,但当然内在函数仍然可以使用256位向量).-march=bdver2(打桩机),bdver3(压路机),bdver4(挖掘机).与推土机相同.它们a[i] += b[i]使用软件预取自动向量化FP 循环,并且足够展开以仅为每个高速缓存行预取一次!-march=znver1(Zen):-mavx256-split-unaligned-store但不是加载,仍然只有128位自动向量化,但这次没有SW预取.-march=btver2(AMD Fam16h,又称Jaguar):既没有启用分割选项,也像Bulldozer系列那样仅使用128位向量+ SW预取自动向量化.-march=eden-x4(通过带有AVX2的Eden):既没有启用拆分选项,但是该-march选项甚至没有启用-mavx,自动矢量化使用movlps/ movhps8字节加载,这真的很愚蠢.至少使用movsd而不是movlps打破虚假依赖.但是如果启用-mavx,它将使用128位未对齐的负载.这里真的很奇怪/不一致的行为,除非有一些奇怪的前端.
options(作为-march = sandybridge的一部分启用,例如,也可能是Bulldozer-family(-march = bdver2是打桩机).但是,当编译器知道内存已对齐时,这并不能解决问题.
脚注1:AMD Piledriver有一个性能错误,使得256位存储吞吐量很糟糕:vmovaps [mem], ymm根据Agner Fog的microarch pdf(https://agner.org/optimize/),即使是每17到20个时钟运行一次的商店.Bulldozer或Steamroller/Excavator中不存在此效果.
Agner Fog表示Bulldozer/Piledriver上的256位AVX吞吐量(特别是没有加载/存储)通常比128位AVX差,部分原因是它不能以2-2 uop模式解码指令.压路机使256位接近收支平衡(如果不花费额外的洗牌).但寄存器寄存器vmovaps ymm指令仍然只能从Bulldozer系列的低128位的mov-elimination中受益.
但是闭源软件或二进制发行版通常不具备-march=native在每个目标架构上构建的奢侈品,因此在制作可以在任何支持AVX的CPU上运行的二进制文件时需要权衡.只要在其他CPU上没有灾难性的缺点,在某些CPU上使用256位代码获得大的加速通常是值得的.
拆分未对齐的加载/存储是为了避免某些CPU出现大问题.在最近的CPU上,它需要额外的uop吞吐量和额外的ALU uops.但至少vinsertf128 ymm, [mem], 1在Haswell/Skylake的端口5上不需要shuffle单元:它可以在任何矢量ALU端口上运行.(并且它没有微熔丝,因此前端带宽需要2 uops.)
PS:
大多数代码都不是由前沿编译器编译的,因此现在更改"通用"调优需要一段时间才能使用更新调优编译的代码.(当然,大多数代码都是用-O2or 编译的-O3,而且这个选项只会影响AVX代码.不过很多人不幸使用-O3 -mavx2而不是-O3 -march=native.所以他们可能会错过FMA,BMI1/2,popcnt和其他东西他们的CPU支持.
GCC的通用调优拆分了未对齐的256位负载,以帮助旧处理器.(我相信,后续更改可避免在通用调优中分割负载.)
您可以使用类似的更近的英特尔CPU调-mtune=intel或-mtune=skylake,你会得到一个指令,如预期.
| 归档时间: |
|
| 查看次数: |
464 次 |
| 最近记录: |