2012-04-16 18 views
6

私は、ベクトル組み込み関数を使用して自分のパーソナル画像処理ライブラリを書き直してSIMD機能を使用する方法を学んでいます。一つの基本的な機能は、単純な「アレイ+=」、すなわち任意の配列の長さについては任意の配列長のSIMD配列の追加

void arrayAdd(unsigned char* A, unsigned char* B, size_t n) { 
    for(size_t i=0; i < n; i++) { B[i] += A[i] }; 
} 

、明白なSIMDコード(16で並ぶと仮定)であるようなものである:

size_t i = 0; 
__m128i xmm0, xmm1; 
n16 = n - (n % 16); 
for (; i < n16; i+=16) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
} 
for (; i < n; i++) { B[i] += A[i]; } 

は、しかし、それはすることが可能ですdo すべて SIMD命令での追加?余分な要素のための

__m128i mask = (0x100<<8*(n - n16))-1; 
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i)); 

、それは未定義の動作になります:私はこれをしようと思いましたか? maskは、実際には配列境界を超えてアクセスが行われないことを保証する必要があります(私は思う)。代わりに最初に余分な要素を実行することですが、配列はn-n16で整列する必要がありますが、それは正しいとは思われません。

ベクトル化されたループのような、より最適なパターンがありますか?

+0

。しかしエピローグはスピードの点では重要ではありません。 – Walter

答えて

4

1つのオプションは、配列を16バイトの倍数に埋め込むことです。次に、128ビットのロード/追加/ストアを実行して、気になるポイントに続く結果を単純に無視します。

バイト単位のオーバーヘッド "エピローグ"は非常に小さくなりますが、大きな配列の場合は、ループをアンロールすることは、より多くのような何かパフォーマンスを向上させることがあります。

for (; i < n32; i+=32) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm2 = _mm_load_si128((__m128i*) (A + i + 16)); 
    xmm3 = _mm_load_si128((__m128i*) (B + i + 16)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    xmm3 = _mm_add_epi8(xmm2, xmm3); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
    _mm_store_si128((__m128i*) (B + i + 16), xmm3); 
} 
// Do another 128 bit load/add/store here if required 

をしかし、それはいくつかのプロファイリングを行うことなしに言うのは難しいです。

最後にアライメントされていないロード/ストアを実行することもできます(16バイトを超えると仮定します)。これはおそらく大きな違いはありません。例えば。あなたは20あなたは0を相殺するために1のロード/ストアを行うバイト、別の非整列負荷を持っている場合は/あなたが_mm_maskmoveu_si128を使用することができますが、XMMレジスタにマスクを取得する必要があります。4.

を相殺するために/ストア(_mm_storeu_si128__mm_loadu_si128)を追加あなたのサンプルコードは動作しません。おそらく、すべてのFFにマスクレジスタを設定し、シフトを使ってそれを整列させたいと思うでしょう。一日の終わりには、おそらくアラインされていないロード/追加/ストアよりも遅くなるでしょう。

これは次のようになります:あなたのコードで配列の長さは常に16バイト(おそらく少ない要素が実際に使用されているが)の倍数であるので、このエピローグが起動することはありませんことを確認することができ

mask = _mm_cmpeq_epi8(mask, mask); // Set to all FF's 
mask = _mm_srli_si128(mask, 16-(n%16)); // Align mask 
_mm_maskmoveu_si128(xmm, mask, A + i); 
+0

実際には、マスクをルックアップテーブルに入れます。あなたはまだ "エピローグ"ループよりも遅いと思いますか? –

+0

@reve_etrange:遅くなる可能性は低いですが、2つのソリューションを測定しなければわかりにくいです。試してみる。 –

+0

私はそれを打つつもりです。しかし、それは合法的なメモリアクセスですか? * maskの値によっては配列境界違反が発生する可能性があります。 –