neon使用和建議

1.neon的使用方法

  • NEON優化庫(Optimized libraries)
  • 向量化編譯器(Vectorizing compilers)
  • NEON intrinsics
  • NEON assembly

       根據優化程度需求不同,第4種最爲底層,若熟練掌握效果最佳,一般也會配合第3種一起使用。本文將會重點介紹第3、4種方法。先簡要介紹前兩種:

(1)Libraries:直接在程序中調用優化

  • Ne10:一個ARM的開源項目,提供數學運算、圖像處理、FFT函數等。
  • Libyuv :一個包含YUV數據的轉換和擴展功能的開源庫。
  • Skia :一個開源的2D圖形庫,用作谷歌Chrome和Chrome OS、Android、Mozilla Firefox和Firefox OS以及其他許多產品的圖形引擎。
  • OpenMax DL:支持加速視頻編解碼、信號處理、色彩空間轉換等。

(2)Vectorizing compilers:GCC編譯器的向量優化選項

  • 在GCC選項中加入向量化表示能有助於C代碼生成NEON代碼,如-ftree-vectorize。

2.NEON intrinsics

      提供了一個連接NEON操作的C函數接口,編譯器會自動生成相關的NEON指令,支持ARMv7-A或ARMv8-A平臺。所有的intrinsics函數都在:GNU官方說明文檔.

一個簡單的例子:


//add for int array. assumed that count is multiple of 4
 
#include<arm_neon.h>
 
// C version
void add_int_c(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i++)
    dst[i] = src1[i] + src2[i];
  }
}
 
// NEON version
void add_float_neon1(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i += 4)
  {
    int32x4_t in1, in2, out;
    in1 = vld1q_s32(src1);
    src1 += 4;
    in2 = vld1q_s32(src2);
    src2 += 4;
    out = vaddq_s32(in1, in2);
    vst1q_s32(dst, out);
    dst += 4;
  }
}

        代碼中的vld1q_s32會被編譯器轉換成vld1.32 {d0, d1}, [r0]指令,同理vaddq_s32vst1q_s32被轉換成vadd.i32 q0, q0, q0vst1.32 {d0, d1}, [r0]。若不清楚指令意義,請參見ARM® Compiler armasm User Guide - Chapter 12 NEON and VFP Instructions。 

3.NEON asssembly

NEON可以有兩種寫法:

(1)Assembly文件

  • 純彙編文件,後綴爲”.S”或”.s”。注意對寄存器數據的保存。具體對通用寄存器的詳解不是本文的重點,有興趣的讀者請自行補充該部分知識。

(2)inline assembly內聯彙編

  • 優點:在C代碼中嵌入彙編,調用簡單,無需手動存儲寄存器
  • 缺點:有較爲複雜的格式需要事先學習,不好移植到其他語言環境。

比如上述intrinsics代碼產生的彙編代碼爲:

// ARMv7-A/AArch32
void add_float_neon2(int* dst, int* src1, int* src2, int count)
{
  asm volatile (
    "1:                                         \n"
    "vld1.32         {q0}, [%[src1]]!           \n"
    "vld1.32         {q1}, [%[src2]]!           \n"
    "vadd.f32        q0, q0, q1                  \n"
    "subs            %[count], %[count], #4     \n"
    "vst1.32         {q0}, [%[dst]]!            \n"
    "bgt             1b                         \n"
    : [dst] "+r" (dst)
    : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
    : "memory", "q0", "q1"
  );
}

4.NEON優化心得

建議的NEON調優步驟:

  1. 理清所需的寄存器、指令。 建議根據要實現的任務,畫出數據變換流程,和每步所需的具體指令,儘可能找到最優的實現流程。這一步非常關鍵,如果思路出錯或是不夠優化,則會影響使用NEON的效果,並且對程序修改帶來麻煩,一定要找到最優的實現算法哦~

  2. 先實現intrinsics(可選)。 初學者先實現intrinsics是有好處的,字面理解性更強,且有助於理解NEON指令。建議隨時打印關鍵步驟的數據,以檢查程序的正誤。

  3. 寫成彙編進一步優化。 將intrinsics生成的彙編代碼進行優化調整。一般來說,有以下幾點值得注意【乾貨】

    • 只要intrinsics運算指令足夠精簡,運算類的彙編指令就不用大修;
    • 大部分的問題會出在存取、移動指令的濫用、混亂使用上;
    • 優化時要儘量減少指令間的相關性,包括結構相關、數據相關控制相關,保證流水線執行效率更高;
    • 大概估算所有程序指令取指、執行、寫回的總理論時間,以此估算本程序可以優化的空間;
    • 熟練對每條指令準備發射、寫回時間有一定的認識,有助於對指令的優化排序;
    • 一定要多測試不同指令的處理時間!!原因是你所想跟實際有出入,且不同的編譯器優化的效果可能也有些不同;
    • 一定要有一定的計算機體系結構基礎,對存儲結構、流水線有一定的體會!!

【注意】在此筆者溫馨提示各位看官(⊙o⊙)不僅是NEON,所有的性能優化是個經驗活兒,需要自己動手才能領悟更多的訣竅,總結一下NEON優化就是:

  • 第一優化算法實現流程;
  • 第二優化程序存取;
  • 第三優化程序執行;
  • 第四哪兒能優化,就優化哪兒~~

對NEON優化使用的好壞直接導致優化效果,優化效果好的會節省70%以上的時間。

5.內聯彙編使用心得

       當讀者熟練後就可以直接上手內聯彙編了。時間有限,本文中不具體介紹inline assembly的使用方法,我後續可能會將這部分單獨寫成一篇博客。感興趣者請參見ARM GCC Inline Assembler Cookbook

一些使用心得:

  • inline assembly下面的三個冒號一定要注意
    • output/input registers的寫法一定要寫對,clobber list也一定要寫完全,否則會造成令你頭疼的問題 (T-T) …
    • 這個問題在給出的cookbook中也有介紹,但是並不全面,有些問題只有自己碰到了再去解決。 筆者就曾經被虐了很久,從生成的彙編發現編譯器將寄存器亂用,導致指針操作完全混亂,毫無頭緒…
    • 一般情況下建議的寫法舉例:

asm volatile (
  ... /* assembly code */
 
  : "+r"(arg0)  // %0
    "+r"(arg1)  // %1  // Output Registers
  : "r"(arg2)   // %2  // Input Registers
  : "cc", "memory", r0, r1
  • 傳入內聯彙編程序段的C參數是有限的
  • 筆者親測對於Cortex-A7平臺output/input registers基本在9以內纔可保證,否則會報出can't find a register in class 'GENERAL_REGS' while reloading 'asm'錯誤。

 

 

   

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