强制 AVX 内部函数改为使用 SSE 指令
Posted
技术标签:
【中文标题】强制 AVX 内部函数改为使用 SSE 指令【英文标题】:Forcing AVX intrinsics to use SSE instructions instead 【发布时间】:2015-11-01 08:57:21 【问题描述】:不幸的是,我有一个 AMD 打桩机 cpu,它似乎与 AVX 指令有问题:
256 位 AVX 寄存器的内存写入异常缓慢。测得的吞吐量比以前的模型(推土机)慢 5 - 6 倍,比两次 128 位写入慢 8 - 9 倍。
根据我自己的经验,我发现 mm256 内在函数比 mm128 慢得多,我假设这是由于上述原因。
我真的很想为最新的指令集 AVX 编写代码,同时仍然能够以合理的速度在我的机器上测试构建。有没有办法强制 mm256 内在函数改用 SSE 指令?我正在使用 VS 2015。
如果没有简单的方法,那么困难的方法呢。将<immintrin.h>
替换为自定义标头,其中包含我自己对可编码为使用SSE 的内在函数的定义?不确定这是否合理,如果可能的话,在我完成这项工作之前,请选择更简单的方法。
【问题讨论】:
我认为没有。他们不会为一个特定的处理器大修他们的编译器。 (只有打桩机有这个错误。) 引用某些内容时应该提供参考。是的,有一个解决方案。 Agner Fog 的矢量类。使用Vec8f
等 AVX 向量并使用 -D__SSE4_2__ -D__XOP__
进行编译。
你用的256位其实更慢?你有对齐问题吗?
【参考方案1】:
使用 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。
【讨论】:
我想知道我该怎么做相反的事情。将__m256
推入向量类库中的Vec8fe
。是的,这没有意义,但我需要这个案例。
@Royii 为什么需要这个案例?如果您有 __m256 表示您已启用 AVX 进行编译,那么 VCL 将使用 Vec8f
而不是 Vec8fe
。
因为在某些情况下,我希望系统中有 2 个不同的代码。一个用于 SSE,一个用于 AVX。 VCL 的问题它只处理其中之一。我希望我可以强制它使用带有Vec8f
的AVX 和带有Vec4f
的SSE。
@Royi,做一个CPU调度器,根据指令集选择代码路径。在某些情况下,使用 Vec8fe 的结果可能比使用两次 Vec4f 更差。我避免使用模拟类型。
我的程序做不到的问题。我有一个仅使用 SSE Intrinsics 构建的函数,以及使用 AVX Intrinsics 构建的相同函数。如何编译不是编译器的决定。我希望它是这样写的。【参考方案2】:
您不想使用 SSE 指令。您想要的是将 256b 存储作为两个单独的 128b 存储来完成,仍然使用 VEX 编码的 128b 指令。即 128b AVX vmovups
.
gcc 有 -mavx256-split-unaligned-load
和 ...-store
选项(例如,作为 -march=sandybridge
的一部分启用,大概也适用于 Bulldozer 系列(-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 (-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 AVX 基本上在硬件中模拟为 SSE 两次,使用非 VEX 编码指令有什么问题?我能想到的唯一优点是使用 AVX 指令,但拆分加载/存储是在指令缓存中使用更少的寄存器和更少的指令。 我猜因为非 VEX 编码指令无法折叠未对齐的负载,这是使用 VEX 编码指令的原因之一。 @Zboson:是的,根据我读到的内容(例如 Agner Fog),在 AMD 上使用 256b 向量通常几乎没有优势。带有 VEX 编码指令的 128b 向量通常是最好的选择。这个答案对于帮助开发/调试使用 Piledriver 机器进行开发的 AVX 软件很有用。您可以使用 256b 内在函数,而不会遇到 256b-store 性能错误。因此,您在 Piledriver 上获得的速度与您编写代码以使用_mm_*
128b 内在函数时的速度大致相同,但希望在英特尔硬件上更快。
对于那些你不能手动编码的存储,比如寄存器溢出,编译器会自动生成存储指令。不能改变那些可以吗?
@Volatile:不,你不能。正确的术语是“溢出”,而不是“溢出”,来描述编译器在用完用于保存所有局部变量和临时变量的寄存器时会做什么。 (即它“将寄存器溢出到堆栈中”。)IDK 如果它可以假设堆栈是 32B 对齐的,那么gcc -mavx256-split-unaligned-store
可能仍会生成一个 2 指令存储。在大多数代码中,溢出很少见(例如,每次调用几次,而不是每次循环迭代)。由于您要避免的问题只是约 17 个周期的性能问题,而不是段错误,因此您很有可能。好的。以上是关于强制 AVX 内部函数改为使用 SSE 指令的主要内容,如果未能解决你的问题,请参考以下文章
MSVC /arch:[指令集] - SSE3、AVX、AVX2