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

Forcing AVX intrinsics to use SSE instructions instead

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

Memory writes with the 256-bit AVX registers are exceptionally slow. The measured throughput is 5 - 6 times slower than on the previous model (Bulldozer), and 8 - 9 times slower than two 128-bit writes.

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

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

没有简单的方法,何谈困难的方法。将 <immintrin.h> 替换为自定义的 header,其中包含我自己对可以编码以使用 SSE 的内在函数的定义?不确定这是否合理,如果可能的话,在我完成这项工作之前更喜欢更简单的方法。

使用 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 位 load/stores 那么您可能仍然需要 VEX 编码指令(尽管目前尚不清楚这是否会有所不同,除了一些特殊情况)。你可以用向量 class 像这样

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。

您不想使用 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 为打桩机 (-march=bdver2) 定义了 __bdver2(Bulldozer 版本 2)。

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

编译器将 _mm256_extractf128(data,0) 优化为简单的转换。 IE。它应该编译为

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 正确生成双指令序列。