【问题标题】:Which one is better, gcc or armcc for NEON optimizations?哪个更好,gcc 或 armcc 用于 NEON 优化?
【发布时间】:2012-09-16 15:40:25
【问题描述】:

在此处参考@auselen 的答案:Using ARM NEON intrinsics to add alpha and permute,看起来 armcc 编译器在 NEON 优化方面比 gcc 编译器要好得多。这是真的吗?我还没有真正尝试过 armcc 编译器。但是我使用带有 -O3 优化标志的 gcc 编译器得到了相当优化的代码。但是现在我想知道armcc是否真的那么好?那么考虑到所有因素,这两个编译器哪个更好?

【问题讨论】:

  • gcc 中的 NEON 支持不如标量整数/fp 支持成熟。但是,auselen 的比较是基于 2.5 年前发布的 gcc 4.4.3。从那时起,NEON 的改进工作已经开展了相当多的工作。同时,armcc 5.01 也只有一年的历史。虽然我仍然预计 armcc 5.02 会领先,但更相关的比较应该是它和 4.7 gcc。
  • @unixsmurf +1 之一是我 :)

标签: embedded arm simd neon cortex-a8


【解决方案1】:

如果您使用 NEON 内在函数,编译器应该没那么重要。大多数(如果不是全部)NEON 内在函数转换为单个 NEON 指令,因此编译器剩下的唯一事情就是寄存器分配和指令调度。根据我的经验,GCC 4.2 和 Clang 3.1 在这些任务上都做得相当不错。

但是请注意,NEON 指令比 NEON 内在指令更具表现力。例如,NEON 加载/存储指令具有前增量和后增量寻址模式,它们将加载或存储与地址寄存器的增量相结合,从而节省了一条指令。 NEON 内在函数没有提供明确的方法来做到这一点,而是依靠编译器将调节器 NEON 加载/存储内在函数和地址增量组合到带有后增量的加载/存储指令中。类似地,一些加载/存储指令允许您指定内存地址的对齐方式,如果您指定更严格的对齐保证,则执行速度更快。同样,NEON 内在函数不允许您显式指定对齐方式,而是依赖编译器来推断正确的对齐说明符。理论上,您在指针上使用“对齐”属性来为编译器提供合适的提示,但至少 Clang 似乎忽略了这些...

根据我的经验,在这些优化方面,Clang 和 GCC 都不是很聪明。幸运的是,这些优化带来的额外性能优势通常并不那么高——它更像是 10% 而不是 100%。

这两个编译器不是特别聪明的另一个领域是避免堆栈溢出。如果您的代码使用的向量值变量多于 NEON 寄存器,那么我似乎两个编译器都会产生可怕的代码。基本上,他们似乎做的是基于有足够可用寄存器的假设来调度指令。寄存器分配似乎是在之后进行的,并且似乎只是在运行寄存器后将值溢出到堆栈中。因此,请确保您的代码在任何时候都具有少于 16 个 128 位向量或 32 个 64 位向量的工作集!

总的来说,我从 GCC 和 Clang 中得到了相当不错的结果,但我经常不得不重新组织代码以避免编译器的特殊性。我的建议是坚持使用 GCC 或 Clang,但请使用您选择的反汇编程序定期检查。

所以,总的来说,我认为坚持使用 GCC 很好。不过,您可能想查看性能关键部件的拆解,并检查它是否合理。

【讨论】:

    【解决方案2】:

    编译器也是软件,它们往往会随着时间的推移而改进。任何像 armcc 这样的通用声明都比 NEON 上的 GCC(或者更好地称为矢量化)不能永远成立,因为一个开发人员小组可以通过足够的关注来缩小差距。然而,最初期望硬件公司开发的编译器性能更好是合乎逻辑的,因为他们需要展示/营销这些功能。

    我最近在 Stack Overflow 上看到的一个例子是关于 answer for branch prediction。引用更新部分的最后一行“这表明即使是成熟的现代编译器在优化代码的能力上也可能存在很大差异......”

    我是 GCC 的忠实粉丝,但我不会将其生成的代码质量与 Intel 或 ARM 的编译器打赌。我希望任何主流商业编译器都能生成至少与 GCC 一样好的代码。

    这个问题的一个经验性答案可能是使用 hilbert-space's neon optimization example 并查看不同的编译器如何优化它。

    void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
    {
      int i;
      uint8x8_t rfac = vdup_n_u8 (77);
      uint8x8_t gfac = vdup_n_u8 (151);
      uint8x8_t bfac = vdup_n_u8 (28);
      n/=8;
    
      for (i=0; i<n; i++)
      {
        uint16x8_t  temp;
        uint8x8x3_t rgb  = vld3_u8 (src);
        uint8x8_t result;
    
        temp = vmull_u8 (rgb.val[0],      rfac);
        temp = vmlal_u8 (temp,rgb.val[1], gfac);
        temp = vmlal_u8 (temp,rgb.val[2], bfac);
    
        result = vshrn_n_u16 (temp, 8);
        vst1_u8 (dest, result);
        src  += 8*3;
        dest += 8;
      }
    }
    

    这是 armcc 5.01

      20:   f421140d    vld3.8  {d1-d3}, [r1]!
      24:   e2822001    add r2, r2, #1
      28:   f3810c04    vmull.u8    q0, d1, d4
      2c:   f3820805    vmlal.u8    q0, d2, d5
      30:   f3830806    vmlal.u8    q0, d3, d6
      34:   f2880810    vshrn.i16   d0, q0, #8
      38:   f400070d    vst1.8  {d0}, [r0]!
      3c:   e1520003    cmp r2, r3
      40:   bafffff6    blt 20 <neon_convert+0x20>
    

    这是 GCC 4.4.3-4.7.1

      1e:   f961 040d   vld3.8  {d16-d18}, [r1]!
      22:   3301        adds    r3, #1
      24:   4293        cmp r3, r2
      26:   ffc0 4ca3   vmull.u8    q10, d16, d19
      2a:   ffc1 48a6   vmlal.u8    q10, d17, d22
      2e:   ffc2 48a7   vmlal.u8    q10, d18, d23
      32:   efc8 4834   vshrn.i16   d20, q10, #8
      36:   f940 470d   vst1.8  {d20}, [r0]!
      3a:   d1f0        bne.n   1e <neon_convert+0x1e>
    

    看起来非常相似,所以我们打成平手。看到这个后,我尝试提到添加 alpha 并再次排列。

    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;
        }
    }
    

    用 gcc 编译...

    $ arm-linux-gnueabihf-gcc --version
    arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-2012.05-20120523 - Linaro GCC 2012.05) 4.7.1 20120514 (prerelease)
    $ arm-linux-gnueabihf-gcc -std=c99 -O3 -c ~/temp/permute.c -marm -mfpu=neon-vfpv4 -mcpu=cortex-a9 -o ~/temp/permute_gcc.o
    
    00000000 <neonPermuteRGBtoBGRA>:
       0:   e3520000    cmp r2, #0
       4:   e2823007    add r3, r2, #7
       8:   b1a02003    movlt   r2, r3
       c:   e92d01f0    push    {r4, r5, r6, r7, r8}
      10:   e1a021c2    asr r2, r2, #3
      14:   e24dd01c    sub sp, sp, #28
      18:   e3520000    cmp r2, #0
      1c:   da000019    ble 88 <neonPermuteRGBtoBGRA+0x88>
      20:   e3a03000    mov r3, #0
      24:   f460040d    vld3.8  {d16-d18}, [r0]!
      28:   eccd0b06    vstmia  sp, {d16-d18}
      2c:   e59dc014    ldr ip, [sp, #20]
      30:   e2833001    add r3, r3, #1
      34:   e59d6010    ldr r6, [sp, #16]
      38:   e1530002    cmp r3, r2
      3c:   e59d8008    ldr r8, [sp, #8]
      40:   e1a0500c    mov r5, ip
      44:   e59dc00c    ldr ip, [sp, #12]
      48:   e1a04006    mov r4, r6
      4c:   f3c73e1f    vmov.i8 d19, #255   ; 0xff
      50:   e1a06008    mov r6, r8
      54:   e59d8000    ldr r8, [sp]
      58:   e1a0700c    mov r7, ip
      5c:   e59dc004    ldr ip, [sp, #4]
      60:   ec454b34    vmov    d20, r4, r5
      64:   e1a04008    mov r4, r8
      68:   f26401b4    vorr    d16, d20, d20
      6c:   e1a0500c    mov r5, ip
      70:   ec476b35    vmov    d21, r6, r7
      74:   f26511b5    vorr    d17, d21, d21
      78:   ec454b34    vmov    d20, r4, r5
      7c:   f26421b4    vorr    d18, d20, d20
      80:   f441000d    vst4.8  {d16-d19}, [r1]!
      84:   1affffe6    bne 24 <neonPermuteRGBtoBGRA+0x24>
      88:   e28dd01c    add sp, sp, #28
      8c:   e8bd01f0    pop {r4, r5, r6, r7, r8}
      90:   e12fff1e    bx  lr
    

    正在用 armcc 编译...

    $ armcc
    ARM C/C++ Compiler, 5.01 [Build 113]
    $ armcc --C99 --cpu=Cortex-A9 -O3 -c permute.c -o permute_arm.o
    
    00000000 <neonPermuteRGBtoBGRA>:
       0:   e1a03fc2    asr r3, r2, #31
       4:   f3870e1f    vmov.i8 d0, #255    ; 0xff
       8:   e0822ea3    add r2, r2, r3, lsr #29
       c:   e1a031c2    asr r3, r2, #3
      10:   e3a02000    mov r2, #0
      14:   ea000006    b   34 <neonPermuteRGBtoBGRA+0x34>
      18:   f420440d    vld3.8  {d4-d6}, [r0]!
      1c:   e2822001    add r2, r2, #1
      20:   eeb01b45    vmov.f64    d1, d5
      24:   eeb02b46    vmov.f64    d2, d6
      28:   eeb05b40    vmov.f64    d5, d0
      2c:   eeb03b41    vmov.f64    d3, d1
      30:   f401200d    vst4.8  {d2-d5}, [r1]!
      34:   e1520003    cmp r2, r3
      38:   bafffff6    blt 18 <neonPermuteRGBtoBGRA+0x18>
      3c:   e12fff1e    bx  lr
    

    在这种情况下,armcc 会生成更好的代码。我认为这证明了fgp's answer above 的合理性。大多数情况下,GCC 会生成足够好的代码,但您应该关注关键部分,或者最重要的是,首先您必须测量/配置文件。

    【讨论】:

    • 尝试在 GCC 中使用 '-marm' 标志,拇指代码在 GCC 中还没有那么成熟,对于 Cortex-A9 中的 thumb2 单元更是如此。
    • 嗯,正如预期的那样,gcc 的寄存器溢出相当广泛。
    猜你喜欢
    • 2016-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    • 2014-03-21
    • 2019-04-01
    • 2020-11-22
    • 1970-01-01
    相关资源
    最近更新 更多