ARM NEON编译优化

        NEON被设计为附加的加载/存储架构,以提供良好的矢量化, 编译器对c/c++等语言有良好的支持,这样可以实现很高水平的并行性。开发者可以为需要高性能的应用程序编写NEON指令来实现相应功能,最重要的是它实现了访问交叉存储在内存中的多个数据流并组织成想要的数据格式。NEON指令代码的编写可以看成是ARM普通编程的一部分,这使得NEON与外部硬件加速器相比,编程更简单、更高效;他还包括用于读取和写入外部存储器的NEON指令,在NEON寄存器和ARM寄存器之间移动数据并执行SIMD操作。

        一个向量化的编译器可以让你的C/C++的代码以某种方式对其进行矢量化能有效地利用NEON硬件,这意味着你可以写基于C的可移植代码并通过NEON指令获得很高的性能。借助于向量化,使得大量的循环迭代计算成倍的缩短。GCC和ARM编译工具链提供使能自动向量化的NEON技术的功能,但由于C和c++标准不包括并发性方面,你可能需要为编译器提供额外的信息以使NEON功能得到更大的发挥,所需的代码需改全部是代码语言标准的一部分,所以不会对代码的跨平台移植造成影响。当编译器能确定程序员的意图时,编译器能很好的进行矢量化编译,前提是你必须对编译器指定恰当的参数,下面就分各种情况来说明NEON的使能方式:

1.  ARM编译工具自动矢量化
        DS-5专业版支持向量化编译器。为了使能自动化矢量必须指定当前处理器支持NEON且说明处理器指令集版本:
        --vectorize          使能矢量化
        --cpu 7-A/Cortex-A8  指定支持NEON功能的处理器架构
        -O2/-O3              指定优化级别
        -Otime               指定优化类型,时间优先

2.  GCC编译工具自动矢量化
        GCC编译器支持自动矢量化设置,如下:
        -ftree-vectorize
        -mfpu=neon
        -mcpu            指定支持NEON功能的处理器架构
        若优化级别设定为-O3则意味着-ftree-vectorize被设定;如果你没有指定-mcpu选项,GCC编译器会指定为内部默认处理器架构。这样的话编译出的程序可能会运行缓慢或者直接不能运行。

3.  裸跑时怎样使能NEON:
        裸跑程序的意思是程序直接跑在硬件平台上,没有操作系统的支持。NEON在reset之后是disable的,需要手动来enable,下面列举了如何手动来enable NEON指令的支持:
         #include <stdio.h>
         // Bare-minimum start-up code to run NEON code
        __asm void EnableNEON(void)
        {
            MRC p15,0,r0,c1,c0,2 // Read CP Access register
           ORR r0,r0,#0x00f00000 // Enable full access to NEON/VFP by enabling access to
           // Coprocessors 10 and 11
           MCR p15,0,r0,c1,c0,2 // Write CP Access register
           ISB
           MOV r0,#0x40000000 // Switch on the VFP and NEON hardware
           MSR FPEXC,r0 // Set EN bit in FPEXC
       }
       程序的编译如下:
              armcc -c -O2 -Otime --cpu=Cortex-A8 --vectorize --debug hello.c -o hello.o
              armlink --entry=EnableNEON hello.o -o hello.axf

4.  怎样在linux stock kernel中使能NEON
          stock kernel是www.kernel.org发布的没有被修改的linux kernel. 如果是居于stock kernel跑应用程序,则无需手动enalbe NEON. 当应用程序遇到第一个NEON指令kernel会自动enalbe NEON功能。若NEON被disable,应用程序试图执行一个NEON指令时,会抛出未定义指令异常,kernel会使用这个异常来enable NEON并实行NEON指令。NEON的使能会被一直保持直到上下文切换。

5.  怎样在linux custom kernel中使能NEON
        编译内核时的linux内核模块配置单可以帮助我们来enable NEON功能:
           • Floating point emulation →   VFP-format floating point maths
           • Floating point emulation →   Advanced SIMD (NEON) Extension support.            
向量优化要求:
       在C/C++语言中没有指定有关于NEON编程的语法,所以便一起不能安全地生成并行代码。然而,开发者可以向编译器提供附加的信息来让编译器确认那些地方需要向量化。不同于intrinsics,这些修改是架构独立的,可以在任何平台上实现向量化,并且在没有被向量化的目标平台上不会产生负面影响。下面是向量化编程的一些主要规则:
      1. 短的,简单的循环有助于向量化;
      2. 避免在循环内使用break来跳出循环;
      3. 尝试让循环迭代次数为2的幂次方;
      4. 尝试让编译器预先知道循环迭代次数;
      5. 循环内的调用必须是内联函数(inlined);
      6. 用数组下标来替代指针访问向量元;
      7. 间接寻址不能被向量化;
      8. 用restrict关键字告诉编译器指针不能指向重复内从区域;
  
有关于循环迭代次数的申明:
        如果一个循环有固定的循环迭代次数或者开发者知道迭代次数为2的幂次方,则在代码中显式申明其关系以便向量化;
       下次的迭代循环次数不能在上一次的循环提供计算得到,否则不能向量化。移除循环内的依赖关系.
        使用restrict关键字, C99标准引进restrict关键字,这意味着你可以用它来限制指针的访问作用域;
  
通过向量化编译器生成NEON代码
       如果编译器能自动识别C/C++编写的程序的意图,则编译器会进行高效的优化。
      尽管编译器可以在不修改源码的情况下生成一些NEON代码,所以高效风格的代码可以使优化更优。

编译器命令行选项:
      通过编译器优化选项-O2/-O3/Otime/vectorize/cpu可以告诉编译器生成NEON代码,cpu选项一定要被指定为当前NONE单元被支持的处理器名称。
      由于数组清理和其他的原因,SIMD代码有时比等效的ARM代码开销大。在Cortex-A8平台上生成高效的NEON代码可以用下面的命令行:
      armcc --cpu=Cortex-A8 -O3 -Otime --vectorize ...
      下面是一个C语言编译后生成NEON Code的例子:
       /* file.c */
       unsigned int vector_add_of_n(unsigned int* ptr, unsigned int items)
       {
           unsigned int result=0;
           unsigned int i;
           for (i=0; i<(items*4); i+=1)
           {
               result+=ptr[i];
           }
           return result;
        }

        编译命令行: armcc --cpu=Cortex-A8 -O3 –c -Otime –-vectorize file.
        生成的NEON Code: formelf -c file.o
                                vector_add_of_n PROC
                                LSLS r3,r1,#2
                                MOV r2,r0
                                MOV r0,#0
                                BEQ |L1.72|
                                LSL r3,r1,#2
                                VMOV.I8 q0,#0
                                LSRS r1,r3,#2
                                BEQ |L1.48| |L1.32|
                                VLD1.32 {d2,d3},[r2]!
                                VADD.I32 q0,q0,q1
                                SUBS r1,r1,#1
                                BNE |L1.32| |L1.48|
                                CMP r3,#4
                                BCC |L1.72|
                                VADD.I32 d0,d0,d1
                                VPADD.I32 d0,d0,d0
                                VMOV.32 r1,d0[0]
                                ADD r0,r0,r1 |L1.72|
                                BX lr

向量化的例子:
       下面的C函数是一个向量化的例子,从C语言的角度去看代码,很难发现优化的方案。
        Void add_int (int * __restrict pa, int * __restrict pb, unsigned int n, int x)
        {
            unsigned int i;
            for (i = 0; i < (n & ~3); i++)
                pa[i] = pb [i] + x;
         }
      但是,当把前面的函数重写成如下的代码时,优化就显而易见了。
                                                                            
下面的展示了此函数在编译阶段没有进行向量优化和有向量优化两种情况下生成的汇编代码:
       没有向量优化的代码全是ARM指令实现的,而进行向量优化的代码中有NEON指令用来加速数据的load,add,save等关键耗时操作。
                                                 
相同代码在不同编译条件下的对比:
       1. 代码空间优化:
                  编译器选项-Ospace可指定编译器按空间最优优化,不考虑执行速度;
                  DS-5编译示例: armcc --cpu=Cortex-A8 -O3 -Osapce
       2. 代码时间优化:
                  编译器选项-Otime可指定编译器按执行速度最优优化,不考虑生成代码的长度;
                  DS-5编译示例: armcc --cpu=Cortex-A8 -O3 -Otime
       3. 已知迭代次数的优化;
       4. 使用自动优化选项: --vectorize
                   DS-5编译示例: armcc --cpu=Cortex-A8 -O3 -Otime --vectorize
       5. 使用restrict关键字优化;


NEON汇编和ABI(Application Binary Interface)应用程序二进制接口限制
        为了得到更好的性能,对于编程者来说手工编写NEON代码是最好的选择。GUN编译工具(gxx)和ARM编译工具(armasm)都支持NEON汇编指令,若要写汇编程序,你必须清楚ARM定义的寄存器使用规则。ARM EABI(Embedded Application Binary Interface)指定了那些寄存器是可以作为参数,那些是作为返回值,那些是隐藏不可见的。下图列出了D寄存器作为参数或者返回值来使用时的规则:
                                                  
       使用NEON寄存器的最优顺序:
        1.                D0~D7
        2.                D16~D31
        3.                D8~D15(已保护状态)
        浮点参数的传递过程:
        1. 软浮点:通过ARM通用寄存器R0~R3和栈指针寄存器来传递参数;
        2. 硬浮点:处理器有硬件浮点单元可以将数据传递给NEON寄存器;
 
支持NEON的第三方库
       1.  Ne10, 基于C接口的NEON汇编实现。http://projectne10.github.com/Ne10/.
       2.  OpenMAX,是由Khronos定义的用来处理音频,视频,图片的标准API。由基于NEON来实现的OpenMAX DL库。http://www.khronos.org/openmax/.
       3.  ffmpeg, 开源的多媒体处理软件。 http://ffmpeg.org/.
       4.  Eigen3, 基于C++的线性代数,矩阵库。 eigen.tuxfamily.org/.
       5.  Pixman, 2D图形库, http://pixman.org/.
       6.  x264, 开源的H.264编码器。 http://www.videolan.org/developers/x264.html.
       7.  Math-neon, http://code.google.com/p/math-neon/.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章