zoukankan      html  css  js  c++  java
  • ARM NEON指令集优化理论与实践

    ARM NEON指令集优化理论与实践

    一.简介
    NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bit宽的向量运算(vector operations)。NEON技术从ARMv7开始被采用,目前可以在ARM Cortex-A和Cortex-R系列处理器中采用。NEON在Cortex-A7、Cortex-A12、Cortex-A15处理器中被设置为默认选项,但是在其余的ARMv7 Cortex-A系列中是可选项。NEON与VFP共享了同样的寄存器,但它具有自己独立的执行流水线。

    二. NEON寄存器

     

     三. NEON指令集

    所有的支持NEON指令都有一个助记符V,下面以32位指令为例,说明指令的一般格式:

    V{<mod>}<op>{<shape>}{<cond>}{.<dt>}{<dest>}, src1, src2

    • <mod>
      • Q: The instruction uses saturating arithmetic, so that the result is saturated within the range of the specified data type, such as VQABS, VQSHL etc.
      • H: The instruction will halve the result. It does this by shifting right by one place (effectively a divide by two with truncation), such as VHADD, VHSUB.
      • D: The instruction doubles the result, such as VQDMULL, VQDMLAL, VQDMLSL and VQ{R}DMULH.
      • R: The instruction will perform rounding on the result, equivalent to adding 0.5 to the result before truncating, such as VRHADD, VRSHR.
    • <op> - the operation (for example, ADD, SUB, MUL).
    • <shape> - Shape,即前文中的Long (L), Wide (W), Narrow (N).
    • <cond> - Condition, used with IT instruction.
    • <.dt> - Data type, such as s8, u8, f32 etc.
    • <dest> - Destination.
    • <src1> - Source operand 1.
    • <src2> - Source operand 2.

    注: {} 表示可选的参数。

    比如:

    VADD.I16 D0, D1, D2   @ 16位加法

    VMLAL.S16 Q2, D8, D9  @ 有符号16位乘加

    四.NEON支持的指令总结

    1. 运算:和、差、积、商
    2. 共享的 NEON 和 VFP 指令:涉及加载、多寄存器间的传送、存储

    五.  NEON 优化技术
    在利用NEON优化程序时,有下述几项比较通用的优化技巧。
    1. 降低数据依赖性
    在ARM v7-A NEON指令通常需要3~9个指令周期,NEON指令比ARM指令需要更多周期数。因此,为了减少指令延时,最好避免将当前指令的目的寄存器当作下条指令的源寄存器。如下例所示:

    /***************************************************************/
    // C代码

    float SumSquareError_C(const float* src_a, const float* src_b, int count)

    {

      float sse = 0u;

      int i;

      for (i = 0; i < count; ++i) {

        float diff = src_a[i] - src_b[i];

        sse += (float)(diff * diff);

      }

      return sse;

    }

    // NEON实现一

    float SumSquareError_NEON1(const float* src_a, const float* src_b, int count)

    {

      float sse;

      asm volatile (

        "veor    q8, q8, q8                        "

        "veor    q9, q9, q9                        "

        "veor    q10, q10, q10                      "

        "veor    q11, q11, q11                     "

     

      "1:                                          "

        "vld1.32     {q0, q1}, [%0]!               "

        "vld1.32     {q2, q3}, [%0]!               "

        "vld1.32     {q12, q13}, [%1]!              "

        "vld1.32     {q14, q15}, [%1]!             "

        "subs       %2, %2, #16                    "

        // q0, q1, q2, q3 是vsub的目的地寄存器.

        // 也是vmla的源寄存器。

        "vsub.f32   q0, q0, q12                    "

        "vmla.f32   q8, q0, q0                      "

     

        "vsub.f32   q1, q1, q13                    "

        "vmla.f32   q9, q1, q1                     "

     

        "vsub.f32   q2, q2, q14                    "

        "vmla.f32   q10, q2, q2                    "

     

        "vsub.f32   q3, q3, q15                     "

        "vmla.f32   q11, q3, q3                    "

        "bgt        1b                             "

     

        "vadd.f32   q8, q8, q9                     "

        "vadd.f32   q10, q10, q11                  "

        "vadd.f32   q11, q8, q10                    "

        "vpadd.f32  d2, d22, d23                   "

        "vpadd.f32  d0, d2, d2                     "

        "vmov.32    %3, d0[0]                      "

        : "+r"(src_a),

          "+r"(src_b),

          "+r"(count),

          "=r"(sse)

        :

        : "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11","q12", "q13","q14", "q15");

      return sse;

    }

    // NEON实现二

    float SumSquareError_NEON2(const float* src_a, const float* src_b, int count)

    {

      float sse;

      asm volatile (

        "veor    q8, q8, q8                         "

        "veor    q9, q9, q9                        "

        "veor    q10, q10, q10                     "

        "veor    q11, q11, q11                     "

     

      "1:                                          "

        "vld1.32     {q0, q1}, [%0]!               "

        "vld1.32     {q2, q3}, [%0]!               "

        "vld1.32     {q12, q13}, [%1]!             "

        "vld1.32     {q14, q15}, [%1]!             "

        "subs       %2, %2, #16                    "

        "vsub.f32   q0, q0, q12                    "

        "vsub.f32   q1, q1, q13                    "

        "vsub.f32   q2, q2, q14                    "

        "vsub.f32   q3, q3, q15                    "

       

        "vmla.f32   q8, q0, q0                     "

        "vmla.f32   q9, q1, q1                     "

        "vmla.f32   q10, q2, q2                    "

        "vmla.f32   q11, q3, q3                    "

        "bgt        1b                             "

     

        "vadd.f32   q8, q8, q9                     "

        "vadd.f32   q10, q10, q11                  "

        "vadd.f32   q11, q8, q10                   "

        "vpadd.f32  d2, d22, d23                   "

        "vpadd.f32  d0, d2, d2                     "

        "vmov.32    %3, d0[0]                      "

        : "+r"(src_a),

          "+r"(src_b),

          "+r"(count),

          "=r"(sse)

        :

        : "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13","q14", "q15");

      return sse;

    }

    /***************************************************************/
    在NEON实现一中,我们把目的寄存器立刻当作源寄存器;在NEON实现二中,我们重新排布了指令,并给予目的寄存器尽量多的延时。经过测试实现二比实现一快30%。由此可见,降低数据依赖性对于提高程序性能有重要意义。一个好消息是编译器能自动调整NEON intrinsics以降低数据依赖性。这个利用NEON intrinsics的一个很大优势。

    2. 减少跳转
    NEON指令集没有跳转指令,当需要跳转时,我们需要借助ARM指令。在ARM处理器中,分支预测技术被广泛使用。但是一旦分支预测失败,惩罚还是比较高的。因此我们最好尽量减少跳转指令的使用。其实,在有些情况下,我们可以用逻辑运算来代替跳转,如下例所示:

    ARM NEON指令集提供了下列指令来帮助用户实现上述逻辑实现:

    /***************************************************************/
    // C实现

    if( flag )

    {

            dst[x * 4]     = a;

            dst[x * 4 + 1] = a;

            dst[x * 4 + 2] = a;

            dst[x * 4 + 3] = a;

    }

    else

    {

            dst[x * 4]     = b;

            dst[x * 4 + 1] = b;

            dst[x * 4 + 2] = b;

            dst[x * 4 + 3] = b;

    }

    // NEON实现

    //dst[x * 4]     = (a&Eflag) | (b&~Eflag);

    //dst[x * 4 + 1] = (a&Eflag) | (b&~Eflag);

    //dst[x * 4 + 2] = (a&Eflag) | (b&~Eflag);

    //dst[x * 4 + 3] = (a&Eflag) | (b&~Eflag);

     

    VBSL qFlag, qA, qB

    /***************************************************************/
    • VCEQ, VCGE, VCGT, VCLE, VCLT……
    • VBIT, VBIF, VBSL……
    减少跳转,不仅仅是在NEON中使用的技巧,是一个比较通用的问题。即使在C程序中,这个问题也是值得注意的。

    3. 其它技巧
    在ARM NEON编程时,一种功能有时有多种实现方式,但是更少的指令不总是意味着更好的性能,要依据测试结果和profiling数据,具体问题具体分析。下面列出来我遇到的一些特殊情况。
    4. 浮点累加指令
    通常情况下,我们会用VMLA/VMLS来代替VMUL + VADD/ VMUL + VSUB,这样使用较少的指令,完成更多的功能。但是与浮点VMUL相比,浮点VMLA/VMLS具有更长的指令延时,如果在指令延时中间不能插入其它计算的情况下,使用浮点VMUL + VADD/ VMUL + VSUB反而具有更好的性能。
    一个真实例子就是Ne10库函数的浮点FIR函数。代码片段如下所示:
    实现1:在两条VMLA指令之间,仅有VEXT指令。而根据指令延时表,VMLA需要9个周期。
    实现2:对于qAcc0,依然存在指令延时。但是VADD/VMUL只需要5个周期。

  • 相关阅读:
    开发工具 之 PowerDesigner 应用积累
    PowerDesigner 之 PDM建模
    开发工具 之 PowerDesigner
    LCD 和 LED 的区别?
    图像色彩空间YUV和RGB的差别
    ubuntu使用中的一些问题
    FFMPEG-数据结构解释(AVCodecContext,AVStream,AVFormatContext)
    Winform的多线程问题
    C#子线程更新UI控件的方法总结
    malloc(0)的问题
  • 原文地址:https://www.cnblogs.com/wujianming-110117/p/12829345.html
Copyright © 2011-2022 走看看