2013-02-19 23 views
15

私は、SSEを使ってベクトル(u)で4x4行列(M)乗算を最も効率的に実装しようとしています。私はムー= Vを意味SSEとの効率的な4x4行列ベクトル乗算:水平加算と点積 - 何がポイントですか?

は、私の知る限り理解し、このついて行くには主に次の2つの方法があります:。

method 1) v1 = dot(row1, u), v2 = dot(row2, u), v3 = dot(row3, u), v4 = dot(row4, u) 
    method 2) v = u1 col1 + u2 col2 + u3 col3 + u4 col4. 

は、方法2は、SSE2に実装が容易です。方法1は、SSE3の水平加算命令またはSSE4のドット積命令のいずれかを使用して実装できます。しかし、すべての私のテストでは、メソッド2はメソッド1より常に優れています。

メソッド1は利点がありますが、1つの場所は、例えばアフィン変換の3x4マトリックスです。この場合、最後のドット積は不要です。しかし、この場合でも、4x4行列の方法2は、3x4行列の方法1より高速です。私が見つけた唯一の方法は、4x4マトリックス上のメソッド2より速い方法が4x3マトリックス上のメソッド2です。

水平加算とドットプロダクト命令のポイントは何ですか?実際、ドットプロダクションの命令はこの場合に最悪のパフォーマンスを示します。たぶんそれはデータのフォーマットと関係がありますか?行列がどのように順序付けられているかを定義できない場合は、転置が必要であり、その場合、方法1はより良いでしょうか?

いくつかのコードについては下記を参照してください。

__m128 m4x4v_colSSE(const __m128 cols[4], const __m128 v) { 
    __m128 u1 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(0,0,0,0)); 
    __m128 u2 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(1,1,1,1)); 
    __m128 u3 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(2,2,2,2)); 
    __m128 u4 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(3,3,3,3)); 

    __m128 prod1 = _mm_mul_ps(u1, cols[0]); 
    __m128 prod2 = _mm_mul_ps(u2, cols[1]); 
    __m128 prod3 = _mm_mul_ps(u3, cols[2]); 
    __m128 prod4 = _mm_mul_ps(u4, cols[3]); 

    return _mm_add_ps(_mm_add_ps(prod1, prod2), _mm_add_ps(prod3, prod4)); 
} 

__m128 m4x4v_rowSSE3(const __m128 rows[4], const __m128 v) { 
    __m128 prod1 = _mm_mul_ps(rows[0], v); 
    __m128 prod2 = _mm_mul_ps(rows[1], v); 
    __m128 prod3 = _mm_mul_ps(rows[2], v); 
    __m128 prod4 = _mm_mul_ps(rows[3], v); 

    return _mm_hadd_ps(_mm_hadd_ps(prod1, prod2), _mm_hadd_ps(prod3, prod4)); 
} 

__m128 m4x4v_rowSSE4(const __m128 rows[4], const __m128 v) { 
    __m128 prod1 = _mm_dp_ps (rows[0], v, 0xFF); 
    __m128 prod2 = _mm_dp_ps (rows[1], v, 0xFF); 
    __m128 prod3 = _mm_dp_ps (rows[2], v, 0xFF); 
    __m128 prod4 = _mm_dp_ps (rows[3], v, 0xFF); 

    return _mm_shuffle_ps(_mm_movelh_ps(prod1, prod2), _mm_movelh_ps(prod3, prod4), _MM_SHUFFLE(2, 0, 2, 0)); 
} 

答えて

10

水平アドオンと内積命令が複雑である:彼らはちょうど簡単な指示のように、プロセッサによって実行される複数の単純なmicrooperationsに分解されます。最近のIntelプロセッサでは、水平加算は2つのSHUFFLE + 1 ADDマイクロオペレーションに分解され、ドットプロダクトは1 MUL + 1 SHUFFLE + 2 ADDマイクロオペレーションに分解されます。インテルのプロセッサは、マイクロオペレーションの数が多いだけでなく、プロセッサパイプラインの命令デコーダにも重点を置いています。 AMD Bulldozerでは、これらの複雑な命令の相対コストはさらに高くなります。

+0

これは、命令が遅い理由を説明しています。しかし、なぜ実装されたのか説明していません。しかし、私は今知っていると思う。方法2は、データが配列の構造体(SoA)、すなわち順序付けられた列であることが最適であることを要求する。データが構造体の配列(AoS)、すなわち行の順序である場合、転置が行われなければならず、この場合、方法1ははるかに高速である。 つまり、データを定義できる場合は、AoSではなくSoAにし、方法2を使用します。それ以外の場合は、水平方向に追加して方法1を使用します。マトリクス乗算にドットプロダクション命令を使用しないでください。 –

+1

CPUベンダーは、新しい命令を追加した経緯がありますが、これは非常に便利なことですが、当初は実装するハードウェアがほとんどありませんでした。彼らが十分なプログラムで採用された場合、最終的にハードウェアを追加して実際に命令をより高速化します。第一世代の '' _mm_dp_ps''は、これを行うための通常のSSEやSSE3のアプローチよりも速いわけではありませんが、理論的にはたくさんのコードブールが必要です。 –

+0

インテルイントリンシクスガイド:[link](https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE3,SSE4_1&cats=Arithmetic&expand=2737,2084)を見ると、パフォーマンスの数字が表示されます。これはまた、なぜdp-solutionがhadd-solutionによってさえもはるかに優れているのかを説明する助けになるはずです。 – St0fF

関連する問題