質問

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製品を作成できます ab:

__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の記事があります ここ ドット製品の実装に触れます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top