【问题标题】:Using ARM NEON intrinsics to add alpha and permute使用 ARM NEON 内部函数添加 alpha 和置换
【发布时间】:2012-08-07 03:01:33
【问题描述】:

我正在开发一个 iOS 应用程序,它需要相当快地从 RGB -> BGRA 转换图像。如果可能,我想使用 NEON 内在函数。有没有比简单地分配组件更快的方法?

void neonPermuteRGBtoBGRA(unsigned char* src, unsigned char* dst, int numPix)
{
    numPix /= 8; //process 8 pixels at a time

    uint8x8_t alpha = vdup_n_u8 (0xff);

    for (int i=0; i<numPix; i++)
    {
        uint8x8x3_t rgb  = vld3_u8 (src);
        uint8x8x4_t bgra;

        bgra.val[0] = rgb.val[2]; //these lines are slow
        bgra.val[1] = rgb.val[1]; //these lines are slow 
        bgra.val[2] = rgb.val[0]; //these lines are slow

        bgra.val[3] = alpha;

        vst4_u8(dst, bgra);

        src += 8*3;
        dst += 8*4;
    }


}

【问题讨论】:

    标签: arm neon intrinsics cortex-a8


    【解决方案1】:

    ARMCC 的反汇编也没有那么快:

    • 没有使用最合适的说明

    • 它将 VFP 指令与 NEON 指令混合在一起,每次都会导致巨大的打嗝

    试试这个:

      mov r2, r2, lsr #3
      vmov.u8, d3, #0xff
    loop:
      vld3.8 {d0-d2}, [r0]!
      subs r2, r2, #1
      vswp d0, d2
      vst4.8 {d0-d3}, [r1]!
      bgt loop
    
      bx lr
    

    我建议的代码也没有完全优化,但进一步的“真正”优化会严重损害可读性。所以我停在这里。

    【讨论】:

    • 无法将其转换为 llvm 内联汇编。不得不将vmov.u8, d3, #0xff 更改为vmov.u8 d3, #0xff。当包装在上面的函数中时仍然会导致崩溃。有什么想法吗?
    • void neonPermuteRGBtoBGRA_gas(unsigned char* src, unsigned char* dst,int numPix) { __asm__ volatile( "mov r2, r2, lsr #3\n" "vmov.u8 d3, #0xff\n" "loop:\n" "vld3.8 {d0-d2}, [r0]!\n" "subs r2, r2, #1\n" "vswp d0, d2\n" "vst4.8 {d0-d3}, [r1]!\n" "bgt loop\n" "bx lr\n" ); } #endif
    • 我并没有声称 armcc 应该是最佳的。我想说的是,如果您使用 NEON 内在函数,您就掌握在编译器的手中。
    • Neon 和 VFP 寄存器组不一样吗? (1),因此您在其中的移动方式无关紧要 (2)。 infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0002a/…infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/…
    • @NickLee,如果你对最快的循环感兴趣,你应该做一些计时并停止使用内在函数。如果你这样做,我们可以讨论展开、预加载等。
    【解决方案2】:

    这取决于编译器。例如,当我用 armcc (5.01) 编译上面的代码并反汇编时,我得到的结果是这样的(我只是将循环放在循环之外,然后将 alpha 赋值移到了循环之外)

      18:   f420440d    vld3.8  {d4-d6}, [r0]!
      1c:   e2822001    add r2, r2, #1  ; 0x1
      20:   eeb01b45    fcpyd   d1, d5
      24:   eeb00b46    fcpyd   d0, d6
      28:   eeb02b44    fcpyd   d2, d4
      2c:   f401000d    vst4.8  {d0-d3}, [r1]!
      30:   e1520003    cmp r2, r3
      34:   bafffff7    blt 18 <neonPermuteRGBtoBGRA_armcc+0x18>
    

    如果我用 gcc (4.4.3) 编译代码并再次反汇编我得到,

      40:   f967 040f   vld3.8  {d16-d18}, [r7]
      44:   46d6        mov lr, sl
      46:   ecca 0b06   vstmia  sl, {d16-d18}
      4a:   9d02        ldr r5, [sp, #8]
      4c:   ed8d 8b1a   vstr    d8, [sp, #104]
      50:   3718        adds    r7, #24
      52:   e8be 000f   ldmia.w lr!, {r0, r1, r2, r3}
      56:   f108 0801   add.w   r8, r8, #1  ; 0x1
      5a:   c50f        stmia   r5!, {r0, r1, r2, r3}
      5c:   eddd 0b24   vldr    d16, [sp, #144]
      60:   e89e 0003   ldmia.w lr, {r0, r1}
      64:   edcd 0b16   vstr    d16, [sp, #88]
      68:   eddd 0b22   vldr    d16, [sp, #136]
      6c:   edcd 0b18   vstr    d16, [sp, #96]
      70:   e885 0003   stmia.w r5, {r0, r1}
      74:   ed9d 0b26   vldr    d0, [sp, #152]
      78:   9d03        ldr r5, [sp, #12]
      7a:   ed8d 0b14   vstr    d0, [sp, #80]
      7e:   cd0f        ldmia   r5!, {r0, r1, r2, r3}
      80:   46ae        mov lr, r5
      82:   465d        mov r5, fp
      84:   c50f        stmia   r5!, {r0, r1, r2, r3}
      86:   e89e 000f   ldmia.w lr, {r0, r1, r2, r3}
      8a:   e885 000f   stmia.w r5, {r0, r1, r2, r3}
      8e:   9501        str r5, [sp, #4]
      90:   465d        mov r5, fp
      92:   2100        movs    r1, #0
      94:   2220        movs    r2, #32
      96:   4620        mov r0, r4
      98:   f7ff fffe   bl  0 <memset>
      9c:   cd0f        ldmia   r5!, {r0, r1, r2, r3}
      9e:   4625        mov r5, r4
      a0:   c50f        stmia   r5!, {r0, r1, r2, r3}
      a2:   f8dd c004   ldr.w   ip, [sp, #4]
      a6:   e89c 000f   ldmia.w ip, {r0, r1, r2, r3}
      aa:   e885 000f   stmia.w r5, {r0, r1, r2, r3}
      ae:   ecd4 0b08   vldmia  r4, {d16-d19}
      b2:   f946 000f   vst4.8  {d16-d19}, [r6]
      b6:   3620        adds    r6, #32
      b8:   45c8        cmp r8, r9
      ba:   dbc1        blt.n   40 <memset+0x40>
    

    armcc 的执行时间快了 10 倍。

    如果我用 gcc(内联汇编)编译 armcc 为函数生成的汇编代码(看起来现在 alpha 又回到了循环中:))

      void neonPermuteRGBtoBGRA_gas(unsigned char* src, unsigned char* dst,
        int numPix) {
        asm(
                "        ASR      r3,r2,#31\n"
                "        VMOV.I8  d1,#0xff\n"
                "        ADD      r2,r2,r3,LSR #29\n"
                "        ASR      r3,r2,#3\n"
                "        MOV      r2,#0\n"
                "        CMP      r3,#0\n"
                "        BLE      end\n"
                "loop:\n"
                "        VLD3.8   {d4,d5,d6},[r0]!\n"
                "        ADD      r2,r2,#1\n"
                "        CMP      r3,r2\n"
                "        VMOV.F64 d3,d5\n"
                "        VMOV.F64 d2,d6\n"
                "        VMOV.F64 d5,d1\n"
                "        VMOV.F64 d0,d4\n"
                "        VST4.8   {d2,d3,d4,d5},[r1]!\n"
                "        BGT      loop\n"
                "end:\n"
                );
      }
    

    gcc 的执行时间也相同。

    最后我建议你要么反汇编你的二进制文件,然后检查编译器是否产生你想要的东西,要么使用汇编。

    顺便说一句,如果你想进一步提高这个函数的执行时间,我建议你看看

    1. arm 的 PLD(预加载数据)指令
    2. 利用循环中所有可能的 neon 指令,例如循环展开(您会注意到实际上带宽将是从内存中加载数据的时间)

    【讨论】:

    • 像你这样的用户是我如此喜欢堆栈溢出的原因。我什至从未想过要尝试使用 ARM C 编译器(这是我第一次不得不如此精细地优化代码)。谢谢!
    猜你喜欢
    • 2013-05-08
    • 1970-01-01
    • 1970-01-01
    • 2020-05-28
    • 2013-09-18
    • 1970-01-01
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    相关资源
    最近更新 更多