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/.

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