visual-c++ - 更快的组装优化方式在 RGB8 和 RGB32 图像之间转换

标签 visual-c++ optimization assembly x86 sse

我正在尝试为 RGB8 到 RGB32 图像转换找到一种程序集优化的方法。

源是一个 8 位灰度图像,目标应该是一个 32 位灰度图像 (​​BGRA),其中第 4 个 channel (alpha) 被忽略。源地址不保证 16 字节对齐,Count 是 16 的倍数,目标地址是 16 字节对齐。

  • 输入:8 位单 channel 灰度图像
  • 输出:32 位 BGRA(忽略 alpha channel )
  • 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:我还尝试将 C 代码与 MS VS2008 SSE 编译器内在函数一起使用。事实证明,编译器生成了大量不必要的内存移动,导致代码比纯汇编慢 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);
    
        }
    }
    

    最佳答案

    会尝试:

    __asm {
    mov esi, 来源
    mov edi, 目的地
    mov ecx, 计数
    movdqu xmm0, xmmword ptr [esi]
    movdqa xmm4, xmmword ptr [面具 + 0]
    movdqa xmm5, xmmword ptr [面具 + 16]
    movdqa xmm6, xmmword ptr [面具 + 32]
    movdqa xmm7, xmmword ptr [面具 + 48]
    l1:
    十二月
    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
    }

    不过还没有对其进行基准测试;这些变化背后的假设:

  • movdqu xmm0,...延迟可以在循环中隐藏得更多一些(您的代码的负载为 xmm0,后跟直接使用该寄存器中的值的指令)
  • add操作两个 reg 以及 cmp并不是所有必要的;地址生成( lea )和隐式零测试 dec/ja可以使用。这样,就不会有 EFLAGSecx 上的操作引起的依赖关系/esi/edi因为循环中唯一的 ALU 操作是递减循环计数器。

  • 最后,这在任何情况下都可能是加载/存储限制,因此算术是“免费游戏”;因此,即使有给定的论点,我也不希望有什么不同。

    如果输入很大,那么去掉“未对齐的头/尾”是有意义的,即为第一个/最后一个 [0..15] 做一个 duff 的设备字节,以及使用 movdqa 的主循环.

    编辑:

    通过 gcc -msse4.2 -O8 -c 运行您的内在源(GCC 4.7.1) 给出了以下程序集:

    .text 节的反汇编:

    0000000000000000 :
    0: 85 d2 测试 edx,edx
    2: 74 76 je 7a
    4: 66 0f 6f 2d 00 00 00 00 movdqa xmm5,XMMWORD PTR [rip+0x0]
    # c
    c: 48 89 f8 mov rax,rdi
    f: 66 0f 6f 25 00 00 00 00 movdqa xmm4,XMMWORD PTR [rip+0x0]
    # 17
    17: 66 0f 6f 1d 00 00 00 00 movdqa xmm3,XMMWORD PTR [rip+0x0]
    # 1f
    1f: 66 0f 6f 15 00 00 00 00 movdqa xmm2,XMMWORD PTR [rip+0x0]
    # 27
    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 添加 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,xm​​m2
    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 子 ecx,edi
    72: 48 83 c6 40 添加 rsi,0x40
    76: 39 ca cmp edx,ecx
    78: 77 b6 ja 30
    7a: f3 c3 repz ret

    这让我非常强烈地想起你最初的汇编代码。如果 MSVC 产生的东西比这更糟糕,我会说这是您使用的编译器(版本)中的错误/限制。

    关于visual-c++ - 更快的组装优化方式在 RGB8 和 RGB32 图像之间转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12004437/

    相关文章:

    assembly - 在 yasm 中指定 ELF32 部分的物理地址?

    c++ - 来自 Windows SDK 的示例在调试配置中工作,在发布中失败。这怎么可能?

    c++ - 写入 Directshow 源过滤器

    mysql - 加快这个 JOIN,MySQL

    c++ - 成员函数中使用的预分配变量作为成员字段是否可以优化性能? (C++)

    assembly - 汇编中main函数开头的栈内存操作

    c++ - GCC 从 c++ 程序生成的汇编代码中的 .cfi 和 .LFE 是什么?

    c++ - 我们如何重置 VS2008 C++ 中的配置

    sockets - 错误 C2678 : binary '==' : no operator found which takes a left-hand operand of type 'std::_Binder<std::_Unforced,SOCKET &,SOCKADDR *,unsigned int>'

    algorithm - worker 和任务数量不等的匈牙利算法