强制 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 正确生成双指令序列。
不幸的是我有一个 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 正确生成双指令序列。