8

RGB8 から RGB32 への画像変換のアセンブリに最適化された方法を見つけようとしています。

ソースは 8 ビットのグレー イメージで、宛先は 32 ビットのグレー イメージ (BGRA) で、4 番目のチャネル (アルファ) は無視されます。ソース アドレスは 16 バイト アラインであるとは限りません。カウントは 16 の倍数です。デスティネーション アドレスは 16 バイト アラインです。

  • 入力: 8 ビット シングル チャネル グレー イメージ
  • 出力: 32 ビット BGRA (アルファ チャンネルは無視されます)
  • COUNT: 画像サイズは 16 の倍数
  • CPU: x86-32 (SSE2/SSE3 可)

これが私の最適化されたアセンブリコードです。さらに高速な変換方法はありますか?

void ConvertGreyToRgb32Assembler(__m128i* Source, __m128i* Destination, unsigned int Count) {
    static unsigned int __declspec(align(64)) Masks[] = {
        0x80000000, 0x80010101, 0x80020202, 0x80030303,
        0x80040404, 0x80050505, 0x80060606, 0x80070707,
        0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
        0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
    };

    __asm {
        mov esi, Source
        mov edi, Destination
        mov edx, Count
        xor ecx, ecx
        movdqa xmm4, xmmword ptr [Masks + 0]
        movdqa xmm5, xmmword ptr [Masks + 16]
        movdqa xmm6, xmmword ptr [Masks + 32]
        movdqa xmm7, xmmword ptr [Masks + 48]
l1:
        movdqu xmm0, xmmword ptr [esi + ecx]
        movdqa xmm1, xmm0
        movdqa xmm2, xmm0
        movdqa xmm3, xmm0
        pshufb xmm0, xmm4
        pshufb xmm1, xmm5
        pshufb xmm2, xmm6
        pshufb xmm3, xmm7
        movntdq [edi + 0], xmm0
        movntdq [edi + 16], xmm1
        movntdq [edi + 32], xmm2
        movntdq [edi + 48], xmm3
        add edi, 64
        add ecx, 16
        cmp ecx, edx
        jb l1
    }
}

いくつかの PUNPCKLBW と PUNPCKHBW を使用する別のアプローチがありますが、それは少し遅いようです。

更新:これは、最適化されていない基本的なアルゴリズムです。

BGRA* Destination = ...
unsigned char* Source ...
for (unsigned int i = 0; i < Size; i++) {
    Destination[i].Blue = Source[i];
    Destination[i].Green = Source[i];
    Destination[i].Red = Source[i]; 
}

PS: MS VS2008 SSE コンパイラ組み込み関数で C コードを使用してみました。コンパイラが大量の不要なメモリ移動を生成したため、純粋なアセンブリよりもコードが 10 ~ 20% 遅くなることが判明しました。

更新 2:これは、組み込み関数のみを使用した同じコードです。

void ConvertGreyToRgb32Assembler(__m128i* Source, __m128i* Destination, unsigned int Count) {
    static const unsigned int __declspec(align(64)) Masks[] = {
        0x80000000, 0x80010101, 0x80020202, 0x80030303,
        0x80040404, 0x80050505, 0x80060606, 0x80070707,
        0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
        0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
    };

    register __m128i m0 = _mm_load_si128((__m128i*) (Masks + 0));
    register __m128i m1 = _mm_load_si128((__m128i*) (Masks + 4));
    register __m128i m2 = _mm_load_si128((__m128i*) (Masks + 8));
    register __m128i m3 = _mm_load_si128((__m128i*) (Masks + 12));

    for (unsigned int i = 0; i < Count / 16; i++) {
        __m128i r0 = _mm_load_si128(Source + i);

        _mm_stream_si128(Destination + (i * 4) + 0, _mm_shuffle_epi8(r0, m0));
        _mm_stream_si128(Destination + (i * 4) + 1, _mm_shuffle_epi8(r0, m1));
        _mm_stream_si128(Destination + (i * 4) + 2, _mm_shuffle_epi8(r0, m2));
        _mm_stream_si128(Destination + (i * 4) + 3, _mm_shuffle_epi8(r0, m3));
    }
}

更新 3:これは、コンパイラによって生成されたコード (美化) です (Visual Studio 2012、すべての最適化がオン):

    push ebp 
    mov ebp, esp 
    mov edx, dword ptr [ebp+8]
    movdqa xmm1, xmmword ptr ds:[Masks + 0]
    movdqa xmm2, xmmword ptr ds:[Masks + 16]
    movdqa xmm3, xmmword ptr ds:[Masks + 32]
    movdqa xmm4, xmmword ptr ds:[Masks + 48]
    push esi 
    test ecx, ecx 
    je l2
    lea esi, [ecx-1] 
    shr esi, 4 
    inc esi
l1:
    mov ecx, edx 
    movdqu xmm0, xmmword ptr [ecx] 
    mov ecx, eax 
    movdqa xmm5, xmm0 
    pshufb xmm5, xmm1 
    movdqa xmmword ptr [ecx], xmm5 
    movdqa xmm5, xmm0 
    pshufb xmm5, xmm2 
    movdqa xmmword ptr [eax+10h], xmm5 
    movdqa xmm5, xmm0
    pshufb xmm5, xmm3
    movdqa xmmword ptr [eax+20h], xmm5 
    lea ecx, [eax+30h]
    add edx, 10h 
    add eax, 40h 
    dec esi 
    pshufb xmm0, xmm4 
    movdqa xmmword ptr [ecx], xmm0 
    jne l1
l2:
    pop esi
    pop ebp
    ret

とのインターリーブmovdqapshufb、いくらか速いようです。

更新 4:これは最適な手作業で最適化されたコードのようです:

   __asm {
        mov esi, Source
        mov edi, Destination
        mov ecx, Count
        movdqu xmm0, xmmword ptr [esi]
        movdqa xmm4, xmmword ptr [Masks + 0]
        movdqa xmm5, xmmword ptr [Masks + 16]
        movdqa xmm6, xmmword ptr [Masks + 32]
        movdqa xmm7, xmmword ptr [Masks + 48]
l1:
        dec ecx
        lea edi, [ edi + 64 ]
        lea esi, [ esi + 16 ]
        movdqa xmm1, xmm0
        movdqa xmm2, xmm0
        movdqa xmm3, xmm0
        pshufb xmm0, xmm4
        movdqa [edi - 64], xmm0
        pshufb xmm1, xmm5
        movdqa [edi - 48], xmm1
        pshufb xmm2, xmm6
        movdqa [edi - 32], xmm2
        pshufb xmm3, xmm7
        movdqa [edi - 16], xmm3
        movdqu xmm0, xmmword ptr [esi]
        ja l1
    }

更新 5:この変換アルゴリズムはpunpck命令を使用します。ただし、この変換ルーチンは、マスクおよび を使用するよりも少し遅くなりpushfbます。

for (unsigned int i = 0; i < Count; i += 16) {
    register __m128i r0 = _mm_load_si128(Source++);
    register __m128i r1 = _mm_unpackhi_epi8(r0, r0);
    register __m128i r2 = _mm_unpacklo_epi8(r0, r0);
    register __m128i r3 = _mm_unpackhi_epi8(r1, r1);
    register __m128i r4 = _mm_unpacklo_epi8(r1, r1);
    register __m128i r5 = _mm_unpackhi_epi8(r2, r2);
    register __m128i r6 = _mm_unpacklo_epi8(r2, r2);

    _mm_store_si128(Destination++, r6);
    _mm_store_si128(Destination++, r5);
    _mm_store_si128(Destination++, r4);
    _mm_store_si128(Destination++, r3);
}

更新 6:完全を期すために、これは 32 ビットから 8 ビットのグレー イメージに変換する逆の方法です。

static void ConvertRgb32ToGrey(const __m128i* Source, __m128i* Destination, unsigned int Count) {
    static const unsigned char __declspec(align(64)) Masks[] = {
        0x00, 0x04, 0x08, 0x0c, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
        0x80, 0x80, 0x80, 0x80, 0x00, 0x04, 0x08, 0x0c, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x04, 0x08, 0x0c, 0x80, 0x80, 0x80, 0x80,
        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x04, 0x08, 0x0c,
    };

    register __m128i m0 = _mm_load_si128((__m128i*) (Masks + 0));
    register __m128i m1 = _mm_load_si128((__m128i*) (Masks + 16));
    register __m128i m2 = _mm_load_si128((__m128i*) (Masks + 32));
    register __m128i m3 = _mm_load_si128((__m128i*) (Masks + 48));

    for (unsigned int i = 0; i < Count / 64; i++) {
        __m128i a = _mm_load_si128(Source + (i * 4) + 0);
        __m128i b = _mm_load_si128(Source + (i * 4) + 1);
        __m128i c = _mm_load_si128(Source + (i * 4) + 2);
        __m128i d = _mm_load_si128(Source + (i * 4) + 3);

        a = _mm_shuffle_epi8(a, m0);
        b = _mm_shuffle_epi8(b, m1);
        c = _mm_shuffle_epi8(c, m2);
        d = _mm_shuffle_epi8(d, m3);

        __m128i e = _mm_or_si128(a, b);
        __m128i f = _mm_or_si128(c, d);
        __m128i g = _mm_or_si128(e, f);

        _mm_stream_si128(Destination + i, g);

    }
}
4

1 に答える 1

6

試してみます:

    __asm {
        mov esi、ソース
        mov edi、Destination
        mov ecx、カウント
        movdqu xmm0、xmmword ptr [esi]
        movdqa xmm4、xmmwordptr[マスク+0]
        movdqa xmm5、xmmwordptr[マスク+16]
        movdqa xmm6、xmmwordptr[マスク+32]
        movdqa xmm7、xmmwordptr[マスク+48]
l1:
        dec ecx
        lea edi、[edi + 64]
        lea esi、[esi + 16]
        movdqa xmm1、xmm0
        movdqa xmm2、xmm0
        movdqa xmm3、xmm0
        pshufb xmm0、xmm4
        pshufb xmm1、xmm5
        pshufb xmm2、xmm6
        pshufb xmm3、xmm7
        movntdq [edi-64]、xmm0
        movntdq [edi-48]、xmm1
        movntdq [edi-32]、xmm2
        movntdq [edi-16]、xmm3
        movdqu xmm0、xmmword ptr [esi]
        ja l1
    }

ただし、ベンチマークは行っていません。これらの変更の背後にある仮定:

  1. レイテンシーはmovdqu xmm0,...ループ内でもう少し隠すことができます(コードには、xmm0そのレジスターの値を使用する命令が直接続く負荷があります)
  2. 2つのaddregの操作だけでcmpなく、実際にはすべてが必要なわけではありません。アドレス生成( )と/leaによる暗黙のゼロテストを使用できます。そうすれば、ループ内の唯一のALU演算がループカウンターをデクリメントするため、//での操作によって引き起こされる依存関係はありません。decjaEFLAGSecxesiedi

結局、これはいずれにせよロード/ストアにバインドされている可能性が高いため、算術演算は「フリーゲーム」です。したがって、与えられた議論があっても、私はほとんど違いを期待していません。

入力が大きい場合は、「位置合わせされていないヘッド/テール」を取り除くこと、つまり、最初/最後の[0..15]バイトに対してダフのデバイスを実行し、メインループを使用することは理にかなっていますmovdqa

編集:

(GCC 4.7.1)を介して組み込みソースを実行するとgcc -msse4.2 -O8 -c、次のアセンブリが得られます。

セクション.textの逆アセンブル:

0000000000000000 <ConvertGreyToRgb32Assembler>:
   0:85 d2テストedx、edx
   2:74 76 je 7a <ConvertGreyToRgb32Assembler + 0x7a>
   4:66 0f 6f 2d 00 00 00 00 movdqa xmm5、XMMWORD PTR [rip + 0x0]
        #c <ConvertGreyToRgb32Assembler + 0xc>
   c:48 89 f8 mov rax、rdi
   f:66 0f 6f 25 00 00 00 00 movdqa xmm4、XMMWORD PTR [rip + 0x0]
        #17 <ConvertGreyToRgb32Assembler + 0x17>
  17:66 0f 6f 1d 00 00 00 00 movdqa xmm3、XMMWORD PTR [rip + 0x0]
        #1f <ConvertGreyToRgb32Assembler + 0x1f>
  1f:66 0f 6f 15 00 00 00 00 movdqa xmm2、XMMWORD PTR [rip + 0x0]
        #27 <ConvertGreyToRgb32Assembler + 0x27>
  27:66 0f 1f 84 00 00 00 00 00 nop WORD PTR [rax + rax * 1 + 0x0]
  30:f3 0f 6f 00 movdqu xmm0、XMMWORD PTR [rax]
  34:48 89 f1 mov rcx、rsi
  37:48 83 c0 10 add rax、0x10
  3b:66 0f 6f c8 movdqa xmm1、xmm0
  3f:66 0f 38 00 cd pshufb xmm1、xmm5
  44:66 0f e7 0e movntdq XMMWORD PTR [rsi]、xmm1
  48:66 0f 6f c8 movdqa xmm1、xmm0
  4c:66 0f 38 00 cc pshufb xmm1、xmm4
  51:66 0f e7 4e 10 movntdq XMMWORD PTR [rsi + 0x10]、xmm1
  56:66 0f 6f c8 movdqa xmm1、xmm0
  5a:66 0f 38 00 c2 pshufb xmm0、xmm2
  5f:66 0f 38 00 cb pshufb xmm1、xmm3
  64:66 0f e7 4e 20 movntdq XMMWORD PTR [rsi + 0x20]、xmm1
  69:66 0f e7 41 30 movntdq XMMWORD PTR [rcx + 0x30]、xmm0
  6e:89 c1 mov ecx、eax
  70:29 f9 sub ecx、edi
  72:48 83 c6 40 rsi、0x40を追加
  76:39 ca cmp edx、ecx
  78:77 b6 ja 30 <ConvertGreyToRgb32Assembler + 0x30>
  7a:f3 c3 repz ret

これは、最初のアセンブリコードを非常に強く思い出させます。MSVCがそれよりも大幅に悪いものを作成する場合、それは使用したコンパイラ(バージョン)のバグ/制限であると言えます。

于 2012-08-17T12:29:51.197 に答える