使用SSE/AVX指令集进行快速的点积运算

9
我希望您能够快速计算具有3或4个分量的向量的点积。我尝试了几种方法,但大多数在线示例使用浮点数组,而我们的数据结构不同。
我们使用16字节对齐的结构体。代码摘录(简化):
struct float3 {
    float x, y, z, w; // 4th component unused here
}

struct float4 {
    float x, y, z, w;
}

在之前的测试中(使用SSE4点积内置函数或FMA),与使用以下常规的c++代码相比,我无法获得加速。

float dot(const float3 a, const float3 b) {
    return a.x*b.x + a.y*b.y + a.z*b.z;
}

在Intel Ivy Bridge / Haswell上使用gcc和clang进行了测试。看来将数据加载到SIMD寄存器中并再次拉出所需的时间会消耗所有好处。
希望得到一些帮助和想法,如何有效地计算我们的float3/4数据结构的点积。SSE4、AVX甚至AVX2都可以。
编辑注:对于4个元素的情况,请参见How to Calculate single-vector Dot Product using SSE intrinsic functions in C。加上遮罩也许适用于3个元素的情况。

3
你检查过生成的汇编代码了吗?对于gcc编译器,你可以使用“-S”开关打开ASM输出功能(输出将写入使用“-o”指定的目标文件)。你的编译选项是什么?是否可能gcc已经生成了SSE代码? - Jonas Schäfer
4
一般来说,仅当您有许多计算而不离开 SSE 寄存器时,SSE 才会加速计算。从您的 dot 函数中可以看出似乎不够(并且您的测试也证实了这一点)。如果您有一个更大的东西,其中包括对 dot() 的调用(理想情况下是调用 dot() 一千次的循环,并且整个循环都可以实现为 SSE)-那么您就有了整体加速的良好机会。 - No-Bugs Hare
4
看到更多上下文会很有帮助,特别是调用dot函数的代码。你是在循环中调用dot吗?例如对于一个float3float4数组? - Paul R
3
注意到了您提到的问题,您需要分摊将数据加载到SSE寄存器的开销。考虑使用不同的数据布局,例如数组结构(SoA)。对于仅需进行一次点积运算而言,SSE并不能起到很大的帮助。垂直计算4个点积运算(而非水平的DPPS)可以利用SIMD,其中一个块可以在先前的块仍在处理时就开始计算。 - Brett Hale
2个回答

7

在代数上,高效的SIMD看起来和标量代码几乎一模一样。因此,正确的做法是同时对四个浮点向量进行操作以进行点积运算(使用SEE时为八个)。

考虑像这样构建您的代码

#include <x86intrin.h>

struct float4 {
    __m128 xmm;
    float4 () {};
    float4 (__m128 const & x) { xmm = x; }
    float4 & operator = (__m128 const & x) { xmm = x; return *this; }
    float4 & load(float const * p) { xmm = _mm_loadu_ps(p); return *this; }
    operator __m128() const { return xmm; }
};

static inline float4 operator + (float4 const & a, float4 const & b) {
    return _mm_add_ps(a, b);
}
static inline float4 operator * (float4 const & a, float4 const & b) {
    return _mm_mul_ps(a, b);
}

struct block3 {
    float4 x, y, z;
};

struct block4 {
    float4 x, y, z, w;
};

static inline float4 dot(block3 const & a, block3 const & b) {
    return a.x*b.x + a.y*b.y + a.z*b.z;
}

static inline float4 dot(block4 const & a, block4 const & b) {
    return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}

注意,最后两个函数看起来与您的标量dot函数几乎相同,只是float变成了float4float4变成了block3block4。这将以最有效的方式进行点积运算。

1
如果您能发布一些结果,展示这个实现与问题中的代码相比实际上有多快,那就太好了!同时,请展示两种情况下的汇编指令以及一些解释! - Nawaz

1
为了充分利用AVX指令集,您需要以不同的维度思考。不要只做一个点积,而是一次性做8个点积。
查阅SoA和AoS之间的区别。如果您的向量采用SoA(数组结构)格式,则在内存中,您的数据看起来像这样:
// eight 3d vectors, called a.
float ax[8];
float ay[8];
float az[8];

// eight 3d vectors, called b.
float bx[8];
float by[8];
float bz[8];

为了将所有的8个a向量与所有的8个b向量相乘,您需要使用三个simd乘法,分别用于x、y和z。

当然,对于点积,您仍然需要进行加法运算,这有点棘手。但是,使用SoA进行向量的乘法、减法、加法非常容易且速度非常快。当AVX-512可用时,您只需使用3个指令即可进行16个3D向量乘法。


你能提供一些好的学习资源吗?我刚开始学这个,谢谢 JJ。 - Joseph Franciscus
2
@JosephFranciscus 我最喜欢的关于SoA的文章是Noel LLopis写的:http://gamesfromwithin.com/data-oriented-design - Bram

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接