gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 15:42:50 33 4
gpt4 key购买 nike

我正在尝试为 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/

    33 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com