cのSSE固有関数を使用してベクトルDOT製品を計算する方法
-
29-09-2019 - |
質問
1つのベクトルの各要素に、他のベクトルの同じインデックスの要素が掛けられている2つのベクトルを掛けようとしています。次に、結果のベクトルのすべての要素を合計して、1つの数値を取得したいと思います。たとえば、計算は、ベクトル{1,2,3,4}および{5,6,7,8}の場合、このようになります。
1*5+2*6+3*7+4*8
基本的に、私は2つのベクトルのドット製品を採取しています。これを行うためのSSEコマンドがあることは知っていますが、コマンドはそれに関連付けられた本質的な関数を持っていません。この時点で、私は自分のCコードにインラインアセンブリを書きたくないので、本質的な関数のみを使用したいと思います。これは一般的な計算のように思えるので、Googleで答えが見つからなかったことに驚きました。
注:SSE 4.2までサポートする特定のマイクロアーキテクチャを最適化しています。
ご協力いただきありがとうございます。
解決
長いベクトルのドット製品を行っている場合は、乗算と通常のものを使用してください _mm_add_ps
内側ループ内の(またはFMA)。 最後まで水平合計を保存します。
しかし、Simdベクターの1つのペアのドット製品をやっている場合:
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の指導テーブル)
古いプロセッサのフォールバックとして、このアルゴリズムを使用してベクターのDOT製品を作成できます 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
, 、特にIntel 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
入力の1つを指すことがあります。
例として指示を貼り付けますが、ベクトル化の一部がループを展開していたため、あまり読み取れません。
Intelの記事があります ここ ドット製品の実装に触れます。