7

私は SIMD/SSE が初めてで、簡単な画像フィルタリング (ぼかし) をしようとしています。以下のコードは、水平方向の単純な [1 2 1] 重み付けを使用して、8 ビット グレー ビットマップの各ピクセルをフィルター処理します。一度に 16 ピクセルの合計を作成しています。

少なくとも私にとって、このコードの非常に悪い点は、挿入/抽出が多く含まれていることです。これはあまりエレガントではなく、おそらくすべてを遅くすることにもなります。シフト時にデータを 1 つの reg から別の reg にラップするより良い方法はありますか?

buf は、16 バイトでアラインされたイメージ データです。w/h は幅と高さで、16 の倍数です。

__m128i *p = (__m128i *) buf;
__m128i cur1, cur2, sum1, sum2, zeros, tmp1, tmp2, saved;
zeros = _mm_setzero_si128();
short shifted, last = 0, next;

// preload first row
cur1 = _mm_load_si128(p);
for (x = 1; x < (w * h) / 16; x++) {
    // unpack
    sum1 = sum2 = saved = cur1;
    sum1 = _mm_unpacklo_epi8(sum1, zeros);
    sum2 = _mm_unpackhi_epi8(sum2, zeros);
    cur1 = tmp1 = sum1;
    cur2 = tmp2 = sum2;
    // "middle" pixel
    sum1 = _mm_add_epi16(sum1, sum1);
    sum2 = _mm_add_epi16(sum2, sum2);
    // left pixel
    cur2 = _mm_slli_si128(cur2, 2);
    shifted = _mm_extract_epi16(cur1, 7);
    cur2 = _mm_insert_epi16(cur2, shifted, 0);
    cur1 = _mm_slli_si128(cur1, 2);
    cur1 = _mm_insert_epi16(cur1, last, 0);
    sum1 = _mm_add_epi16(sum1, cur1);
    sum2 = _mm_add_epi16(sum2, cur2);
    // right pixel
    tmp1 = _mm_srli_si128(tmp1, 2);
    shifted = _mm_extract_epi16(tmp2, 0);
    tmp1 = _mm_insert_epi16(tmp1, shifted, 7);
    tmp2 = _mm_srli_si128(tmp2, 2);
    // preload next row
    cur1 = _mm_load_si128(p + x);
    // we need the first pixel of the next row for the "right" pixel
    next = _mm_extract_epi16(cur1, 0) & 0xff;
    tmp2 = _mm_insert_epi16(tmp2, next, 7);
    // and the last pixel of last row for the next "left" pixel
    last = ((uint16_t) _mm_extract_epi16(saved, 7)) >> 8;
    sum1 = _mm_add_epi16(sum1, tmp1);
    sum2 = _mm_add_epi16(sum2, tmp2);
    // divide
    sum1 = _mm_srli_epi16(sum1, 2);
    sum2 = _mm_srli_epi16(sum2, 2);
    sum1 = _mm_packus_epi16(sum1, sum2);
    mm_store_si128(p + x - 1, sum1);
}
4

2 に答える 2

2

隣接するピクセルを SSE レジスタに保持することをお勧めします。つまり、_mm_slli_si128 / _mm_srli_si128 の結果を SSE 変数に保持し、すべての挿入と抽出を削除します。古い CPU では、挿入/抽出命令は SSE ユニットと汎用ユニットの間の通信を必要とするため、L1 キャッシュにスピルオーバーしたとしても、SSE 内で計算を維持するよりもはるかに遅くなります。

それが完了すると、4 つの 16 ビット シフト (_mm_slli_si128、_mm_srli_si128、除算シフトはカウントしない)のみが存在するはずです。私の提案は、あなたのコードでベンチマークを行うことです.その時までにあなたのコードはすでにメモリ帯域幅の制限に達しているかもしれません..それはあなたがもう最適化できないことを意味します.

画像が大きく (L2 サイズよりも大きく)、出力がすぐに読み戻されない場合は、書き戻しに MOVNTDQ ( _mm_stream_si128 ) を使用してみてください。いくつかの Web サイトによると、SSE2 に含まれていますが、再確認することをお勧めします。

SIMD チュートリアル:

一部の SIMD 教祖の Web サイト:

于 2010-06-30T08:11:32.930 に答える
2

SSE3.5 (別名 SSSE3) が登場し、PALIGNR (_mm_alignr_epi8) が導入されるまで、この種の近隣操作は SSE にとって常に苦痛でした。

ただし、SSE2/SSE3 との下位互換性が必要な場合は、SSE2/SSE3 の _mm_alignr_epi8 をエミュレートし、SSE3.5/SSE4 を対象とする場合は _mm_alignr_epi8 にドロップする同等のマクロまたはインライン関数を作成できます。

もう 1 つの方法は、位置合わせされていないロードを使用してシフトされたデータを取得することです。これは、古い CPU では比較的コストがかかります (位置合わせされたロードの約 2 倍のレイテンシとスループットの半分)。また、現在の Intel CPU (Core i7) では、位置合わせされていないロードは、位置合わせされたロードと比較してペナルティがないため、コードは Core i7 などで非常に効率的であるという利点もあります

于 2010-06-28T07:41:02.583 に答える