如何使用C中的SSE固有函数来计算矢量点产物
-
29-09-2019 - |
题
我试图将两个向量乘以将一个向量的每个元素乘以另一个向量的相同索引中的元素。然后,我想将结果向量的所有元素总结以获得一个数字。例如,对向量{1,2,3,4}和{5,6,7,8}的计算看起来像这样:
1*5+2*6+3*7+4*8
从本质上讲,我正在采用两个向量的点产物。我知道有一个SSE命令可以执行此操作,但是该命令没有与之关联的内在函数。在这一点上,我不想在我的C代码中编写内联汇编,因此我只想使用固有的函数。这似乎是一个常见的计算,所以我自己感到惊讶,我找不到Google上的答案。
注意:我正在针对支持高达第4.2节的特定微观体系结构进行优化。
谢谢你的帮助。
解决方案
如果您正在做较长矢量的点产品,请使用倍数和常规 _mm_add_ps
(或FMA)内部环内。 保存水平和直到结束。
但是,如果您只做一对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);
在英特尔主流CPU(不是Atom/Silvermont)上,这些速度比用多个指令手动进行操作要快一些。
但是在AMD(包括Ryzen)上, dpps
较慢。 (看 Agner Fog的指示表)
作为较老的处理器的后备,您可以使用此算法来创建向量的点产品 a
和 b
:
__m128 r1 = _mm_mul_ps(a, b);
然后水平总和 r1
使用 在x86上进行水平浮点矢量总和的最快方法 (请参阅那里的评论版本,以及为什么它更快。)
__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);
缓慢的替代费用每次洗牌2 hadd
, ,这将很容易在洗牌吞吐量上瓶颈,尤其是在英特尔CPU上。
r2 = _mm_hadd_ps(r1, r1);
r3 = _mm_hadd_ps(r2, r2);
_mm_store_ss(&result, r3);
其他提示
我会说最快的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
}
我跟着 - 在x86上进行水平浮点矢量总和的最快方法.
我写了这本书,并用 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
可能指向其中一个输入。
我将指示粘贴为一个例子,但是由于矢量化的一部分将循环展开,因此不太可读。
英特尔有一篇文章 这里 涉及点产品实现。