强制AVX内在函数使用SSE指令

时间:2015-11-01 08:57:21

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

不幸的是我有一个AMD打桩机cpu,它似乎与AVX指令有问题:

  

使用256位AVX寄存器进行存储器写操作非常慢。测量的吞吐量比之前的型号(Bulldozer)慢5到6倍,比两个128位写入慢8到9倍。

根据我自己的经验,我发现mm256内在函数比mm128要慢得多,我假设是因为上述原因。

我真的想为最新的指令集AVX编写代码,同时仍然能够以合理的速度在我的机器上测试构建。有没有办法迫使mm256内在函数使用SSE指令?我正在使用VS 2015。

如果没有简单的方法,那么艰难的方式呢。将<immintrin.h>替换为包含我自己的内在函数定义的自定义标题,可以对其进行编码以使用SSE?不确定这是多么合理,在我完成这项工作之前,如果可能的话,更喜欢更简单的方法。

2 个答案:

答案 0 :(得分:6)

使用Agner Fog的Vector Class Library并将其添加到Visual Studio中的命令行:-D__SSE4_2__ -D__XOP__

然后使用AVX大小的矢量,例如Vec8f八个浮点数。在没有AVX启用的情况下进行编译时,它将使用文件vectorf256e.h来模拟具有两个SSE寄存器的AVX。例如,Vec8f继承自Vec256fe,其开头如下:

class Vec256fe {
protected:
    __m128 y0;                         // low half
    __m128 y1;                         // high half

如果使用/arch:AVX -D__XOP__进行编译,则VCL将改为使用文件vectorf256.h和一个AVX寄存器。然后你的代码适用于AVX和SSE,只需更改编译器开关。

如果您不想使用XOP,请不要使用-D__XOP__

正如Peter Cordes在他的回答中所指出的,如果你的目标只是为了避免256位加载/存储,那么你可能仍然需要VEX编码指令(虽然不清楚除了某些特殊情况外这会有所不同) 。您可以使用像

这样的矢量类来实现
Vec8f a;
Vec4f lo = a.get_low();  // a is a Vec8f type
Vec4f hi = a.get_high();
lo.store(&b[0]);         // b is a float array
hi.store(&b[4]);

然后使用/arch:AVX -D__XOP__进行编译。

另一种选择是使用Vecnf然后执行

的一个源文件
//foo.cpp
#include "vectorclass.h"
#if SIMDWIDTH == 4
typedef Vec4f Vecnf;
#else
typedef Vec8f Vecnf;
#endif  

并像这样编译

cl /O2 /DSIMDWIDTH=4                     foo.cpp /Fofoo_sse
cl /O2 /DSIMDWIDTH=4 /arch:AVX /D__XOP__ foo.cpp /Fofoo_avx128
cl /O2 /DSIMDWIDTH=8 /arch:AVX           foo.cpp /Fofoo_avx256

这将创建三个带有一个源文件的可执行文件。您可以使用/c编译它们而不是链接它们,而是创建一个CPU调度程序。我使用了XOP和avx128,因为除了AMD之外,我认为没有充分的理由使用avx128。

答案 1 :(得分:3)

您不想使用SSE说明。你想要的是256b商店作为两个独立的128b商店,仍然使用VEX编码的128b指令。即128b AVX vmovups

gcc有-mavx256-split-unaligned-load...-store个选项(例如-march=sandybridge的一部分启用),大概也适用于推土机系列(-march=bdver2是打桩机)。当编译器知道 对齐的内存时,解决问题。

您可以使用像

这样的宏覆盖正常的256b存储内在函数
// maybe enable this for all BD family CPUs?

#if defined(__bdver2) | defined(PILEDRIVER) | defined(SPLIT_256b_STORES)
   #define _mm256_storeu_ps(addr, data) do{ \
      _mm_storeu_ps( ((float*)(addr)) + 0, _mm256_extractf128_ps((data),0)); \
      _mm_storeu_ps( ((float*)(addr)) + 4, _mm256_extractf128_ps((data),1)); \
   }while(0)
#endif

gcc为Piledriver(__bdver2)定义-march=bdver2(Bulldozer版本2)。

您可以对(对齐)_mm256_store_ps执行相同的操作,或者只使用未对齐的内在函数。

编译器将_mm256_extractf128(data,0)优化为简单的强制转换。即它应该只编译到

vmovups       [rdi], xmm0         ; if data is in xmm0 and addr is in rdi
vextractf128  [rdi+16], xmm0, 1

然而,testing on godbolt shows that gcc and clang are dumb,并提取到寄存器和然后存储。 ICC正确生成双指令序列。