我试图将两个向量相乘,其中一个向量的每个元素乘以另一个向量的相同索引中的元素。然后,我想要对结果向量的所有元素求和,以获得一个数字。例如,对于向量{1,2,3,4}和{5,6,7,8}:
,计算结果如下所示1 * 5 + 2 * 6 + 3 * 7 + 4 * 8
基本上,我正在考虑两个向量的点积。我知道有一个SSE命令来执行此操作,但该命令没有与之关联的内部函数。此时,我不想在我的C代码中编写内联汇编,所以我只想使用内部函数。这似乎是一个常见的计算,所以我很惊讶自己在Google上找不到答案。
注意:我正在优化支持SSE 4.2的特定微架构。
感谢您的帮助。
答案 0 :(得分:18)
如果您正在使用较长向量的点积,请在内循环内使用乘法和常规_mm_add_ps
(或FMA)。保存水平和直到结束。< / p>
但是,如果您只使用一对SIMD向量进行点积:
GCC(至少版本4.3)包括<smmintrin.h>
和SSE4.1级内在函数,包括单精度和双精度点积:
_mm_dp_ps (__m128 __X, __m128 __Y, const int __M);
_mm_dp_pd (__m128d __X, __m128d __Y, const int __M);
在Intel主流CPU(不是Atom / Silvermont)上,这些比使用多条指令手动操作要快一些。
但是在AMD(包括Ryzen)上,dpps
明显变慢了。 (见Agner Fog's instruction tables)
作为旧处理器的后备,您可以使用此算法创建向量a
和b
的点积:
__m128 r1 = _mm_mul_ps(a, b);
然后使用Fastest way to do horizontal float vector sum on x86水平求和r1
(请参阅此处的评论版本,以及为什么它更快。)
__m128 shuf = _mm_shuffle_ps(r1, r1, _MM_SHUFFLE(2, 3, 0, 1));
__m128 sums = _mm_add_ps(r1, shuf);
shuf = _mm_movehl_ps(shuf, sums);
sums = _mm_add_ss(sums, shuf);
float result = _mm_cvtss_f32(sums);
一个缓慢的替代方案每hadd
需要2次洗牌,这很容易成为洗牌吞吐量的瓶颈,尤其是在英特尔CPU上。
r2 = _mm_hadd_ps(r1, r1);
r3 = _mm_hadd_ps(r2, r2);
_mm_store_ss(&result, r3);
答案 1 :(得分:4)
我说最快的SSE方法是:
static inline float CalcDotProductSse(__m128 x, __m128 y) {
__m128 mulRes, shufReg, sumsReg;
mulRes = _mm_mul_ps(x, y);
// Calculates the sum of SSE Register - https://stackoverflow.com/a/35270026/195787
shufReg = _mm_movehdup_ps(mulRes); // Broadcast elements 3,1 to 2,0
sumsReg = _mm_add_ps(mulRes, shufReg);
shufReg = _mm_movehl_ps(shufReg, sumsReg); // High Half -> Low Half
sumsReg = _mm_add_ss(sumsReg, shufReg);
return _mm_cvtss_f32(sumsReg); // Result in the lower part of the SSE Register
}
答案 2 :(得分:3)
我写了这个并用gcc -O3 -S -ftree-vectorize -ftree-vectorizer-verbose=2 sse.c
void f(int * __restrict__ a, int * __restrict__ b, int * __restrict__ c, int * __restrict__ d,
int * __restrict__ e, int * __restrict__ f, int * __restrict__ g, int * __restrict__ h,
int * __restrict__ o)
{
int i;
for (i = 0; i < 8; ++i)
o[i] = a[i]*e[i] + b[i]*f[i] + c[i]*g[i] + d[i]*h[i];
}
GCC 4.3.0自动矢量化了它:
sse.c:5: note: LOOP VECTORIZED.
sse.c:2: note: vectorized 1 loops in function.
然而,只有在我使用具有足够迭代的循环时才会这样做 - 否则详细输出会说明矢量化无利可图或循环太小。如果没有__restrict__
个关键字,则必须生成单独的非向量化版本,以处理输出o
可能指向其中一个输入的情况。
我会将指令粘贴为一个示例,但由于部分向量化展开了循环,因此它的可读性不高。
答案 3 :(得分:3)
英特尔有一篇文章here涉及点产品实施。