11

SSE組み込み関数を使用して、Intelx86Nehalemマイクロアーキテクチャ用にいくつかのコードを最適化しています。

私のプログラムの一部は4つの内積を計算し、各結果を配列の連続したチャンクの前の値に追加します。すなわち、

tmp0 = _mm_dp_ps(A_0m, B_0m, 0xF1);
tmp1 = _mm_dp_ps(A_1m, B_0m, 0xF2);
tmp2 = _mm_dp_ps(A_2m, B_0m, 0xF4);
tmp3 = _mm_dp_ps(A_3m, B_0m, 0xF8);

tmp0 = _mm_add_ps(tmp0, tmp1);
tmp0 = _mm_add_ps(tmp0, tmp2);
tmp0 = _mm_add_ps(tmp0, tmp3);
tmp0 = _mm_add_ps(tmp0, C_0n);

_mm_storeu_ps(C_2, tmp0);

各ドット積の結果を保持するために4つの一時xmmレジスタを使用してこれを実行していることに注意してください。各xmmレジスタでは、結果は他の一時的なxmmレジスタに対して一意の32ビットに配置され、最終結果は次のようになります。

tmp0=R0-ゼロ-ゼロ-ゼロ

tmp1=ゼロ-R1-ゼロ-ゼロ

tmp2=ゼロ-ゼロ-R2-ゼロ

tmp3=ゼロ-ゼロ-ゼロ-R3

次の手順でそれらを合計することにより、各tmp変数に含まれる値を1つのxmm変数に結合します。

tmp0 = _mm_add_ps(tmp0, tmp1);
tmp0 = _mm_add_ps(tmp0, tmp2);
tmp0 = _mm_add_ps(tmp0, tmp3);

最後に、ドット積の4つの結果すべてを含むレジスタを配列の連続部分に追加して、配列のインデックスがドット積によって増分されるようにします(C_0nは、更新される配列に現在ある4つの値です)。 ; C_2は、これら4つの値を指すアドレスです):

tmp0 = _mm_add_ps(tmp0, C_0n);
_mm_storeu_ps(C_2, tmp0);

ドット積の結果を取得して、それらを配列の連続するチャンクに追加するための、より回りくどく、より効率的な方法があるかどうかを知りたいです。このようにして、ゼロ以外の値が1つしかないレジスタ間で3つの加算を実行しています。これを回避するためのより効果的な方法があるはずです。

私はすべての助けに感謝します。ありがとうございました。

4

4 に答える 4

6

このようなコードの場合、{A_0m.x、A_1m.x、A_2m.x、A_3m.x}が1つのベクトルなどに格納されるように、AとBの「転置」を格納するのが好きです。乗算と加算だけを使用するドット積。完了すると、シャッフルすることなく、1つのベクトルに4つのドット積がすべて含まれます。

これは、レイトレーシングで頻繁に使用され、平面に対して一度に4つの光線をテストします(たとえば、kdツリーをトラバースする場合)。ただし、入力データを制御できない場合は、トランスポーズを実行するオーバーヘッドは価値がない可能性があります。コードはSSE4より前のマシンでも実行されますが、問題にはならない場合があります。


既存のコードに関する小さな効率のメモ:これの代わりに

tmp0 = _mm_add_ps(tmp0, tmp1);
tmp0 = _mm_add_ps(tmp0, tmp2);
tmp0 = _mm_add_ps(tmp0, tmp3);
tmp0 = _mm_add_ps(tmp0, C_0n);

これを行う方が少し良いかもしれません:

tmp0 = _mm_add_ps(tmp0, tmp1);  // 0 + 1 -> 0
tmp2 = _mm_add_ps(tmp2, tmp3);  // 2 + 3 -> 2
tmp0 = _mm_add_ps(tmp0, tmp2);  // 0 + 2 -> 0
tmp0 = _mm_add_ps(tmp0, C_0n);

最初の2つmm_add_psは完全に独立しています。また、追加とシャッフルの相対的なタイミングはわかりませんが、少し速いかもしれません。


お役に立てば幸いです。

于 2010-11-13T09:12:49.517 に答える
3

SSE3ハッドを使用することも可能です。いくつかの簡単なテストでは、_dot_psを使用するよりも速くなりました。これにより、追加可能な4つのドット積が返されます。

static inline __m128 dot_p(const __m128 x, const __m128 y[4])
{
   __m128 z[4];

   z[0] = x * y[0];
   z[1] = x * y[1];
   z[2] = x * y[2];
   z[3] = x * y[3];
   z[0] = _mm_hadd_ps(z[0], z[1]);
   z[2] = _mm_hadd_ps(z[2], z[3]);
   z[0] = _mm_hadd_ps(z[0], z[2]);

   return z[0];
}
于 2010-12-17T11:03:40.367 に答える
1

ドット積の結果を下位ワードのままにして、スカラーストアop_mm_store_ssを使用して、各m128レジスタからの1つのfloatを配列の適切な場所に保存してみてください。Nehalemのストアバッファは、同じ行に連続した書き込みを蓄積し、それらをバッチでL1にフラッシュする必要があります。

それを行うためのプロの方法は、セリオンの転置アプローチです。MSVCの_MM_TRANSPOSE4_PSマクロが転置を行います。

于 2010-11-13T09:29:01.140 に答える
1

この質問は古いと思いますが、なぜ使用_mm_add_psするのですか?次のように置き換えます。

tmp0 = _mm_or_ps(tmp0, tmp1);
tmp2 = _mm_or_ps(tmp2, tmp3);
tmp0 = _mm_or_ps(tmp0, tmp2);

おそらく_mm_dp_psレイテンシーの一部を隠すことができます。最初の_mm_or_ps製品も最後の2つのドット積を待たず、(高速の)ビット演算です。ついに:

_mm_storeu_ps(C_2, _mm_add_ps(tmp0, C_0));
于 2012-12-22T00:41:44.000 に答える