15

2つのベクトルを掛け合わせようとしています。ここで、一方のベクトルの各要素に、もう一方のベクトルの同じインデックスの要素を掛けます。次に、結果のベクトルのすべての要素を合計して、1つの数値を取得します。たとえば、ベクトル{1,2,3,4}と{5,6,7,8}の計算は次のようになります。

1*5 + 2*6 + 3*7 + 4*8

基本的に、私は2つのベクトルの内積を取ります。これを行うためのSSEコマンドがあることは知っていますが、コマンドには組み込み関数が関連付けられていません。この時点では、Cコードでインラインアセンブリを記述したくないので、組み込み関数のみを使用したいと思います。これは一般的な計算のように思われるので、Googleで答えが見つからなかったことに私は驚いています。

注:SSE4.2までをサポートする特定のマイクロアーキテクチャー向けに最適化しています。

4

4 に答える 4

22

より長いベクトルの内積を実行している場合は_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の指示表を参照してください)


a古いプロセッサのフォールバックとして、このアルゴリズムを使用して、ベクトルとb:の内積を作成できます。

__m128 r1 = _mm_mul_ps(a, b);

次に、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);

遅い代替手段は、1つあたり2シャッフルのコストがかかりますhadd。これは、特にIntel CPUで、シャッフルスループットのボトルネックになりやすくなります。

r2 = _mm_hadd_ps(r1, r1);
r3 = _mm_hadd_ps(r2, r2);
_mm_store_ss(&result, r3);
于 2010-11-08T04:33:39.390 に答える
7

最速の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で水平フロートベクトル和を行う最速の方法

于 2017-03-21T10:25:08.083 に答える
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];
}

そしてGCC4.3.0はそれを自動ベクトル化しました:

sse.c:5: note: LOOP VECTORIZED.
sse.c:2: note: vectorized 1 loops in function.

ただし、それは、十分な反復を伴うループを使用した場合にのみ実行されます。そうでない場合、詳細な出力により、ベクトル化が不採算であるか、ループが小さすぎることが明らかになります。キーワードがないと、出力が入力の1つを指している可能性が__restrict__ある場合に対処するために、ベクトル化されていない個別のバージョンを生成する必要があります。o

例として命令を貼り付けますが、ベクトル化の一部がループを展開したため、あまり読みやすくありません。

于 2010-11-08T01:42:50.400 に答える
3

ドット積の実装に触れているIntelの記事がここにあります。

于 2010-11-08T01:42:59.020 に答える