本文内容基于《NEON Prgrammer’s Guide》(下稱NPG)第二章Compiling NEON Instruction,所有編譯指令都基于gcc-arm
簡介
讓你的程式使用arm核心的NEON unit進行并行計算,提升嵌入式程式性能,一般有3種方法:
- 手撸NEON代碼,無論是彙編還是Intrinsics
- 使用NEON庫
- 讓編譯器将代碼編譯成NEON指令
第1項是NPG的正題,以後再說,這一章還是說一些簡單的,比如第2項和第3項。
NEON庫
NPG中推薦了幾個開源的利用NEON進行優化的函數庫(不過我看的是2013年的NPG,是不是後來又出了其他庫就不知道了),有需要百度庫名即可:
- Ne10,将NEON指令封裝成了C語言函數,實作了一些基礎數學和dsp運算,估計以後會用,用的時候再介紹
- OpenMAX,open media acceleraion,一個多媒體加速庫,有為ARM實作的NEON加速版本
- ffmpeg,搞音視訊的應該比較熟悉,一個音視訊編解碼庫。
- Eigen3,一個線性代數的C++庫,做圖像或者機器人的朋友應該常用
- Pixman,一個2D圖形圖像函數庫
- x264,一個H.264的圖像編碼庫
- Math-noen,另外一個數學操作函數庫
- libjpeg-turbo,這個書裡沒有,是我剛剛用過的jpeg圖像加速編解碼庫
讓編譯器将代碼編譯成NEON指令
NGP書中,一般用vectorization來表示使用NEON指令來進行并行計算加速,是以讓編譯器來幫我們進行vectorization就叫做auto-vectorization。一般有3個步驟:
确定你的處理器有NEON unit
linux環境下指令行輸入
cat /proc/cpuinfo | grep neon
,如果有輸出
Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3
類似的輸出,恭喜你,你的cpu有neon unit。如果沒有,那麼我也沒啥辦法,查查cpu的datasheet吧。
不過要注意的是,NEON unit在開機了之後是預設disable的(上述指令表示你的cpu有NEON unit這個硬體)。
但是Linux系統會在執行程式的時候,第一次遇到NEON指令時,會使能NEON unit,直至遇到上下文切換。是以使用Linux的同學應該不用擔心。如果自己編譯核心的同學,則需要注意一下,在編譯過程中需要在Floating point emulation選項下打開VFP-fomat floating point maths和Advanced SIMD(NEON) Extension support。
對于使用其他OS或者裸機跑程式(ARM裸機,這個有點虎)的同學,則需要手動使能NEON unit,
// 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
}
寫出能被vectorization的代碼
NPG中提供了一些原則,可以幫助我們的code更好的被編譯器實作vetorization,
- 讓循環變得盡可能的簡單和短,比如說,
int a[10]={0}, b[10]={0}, c[10]={0};
// ×
for (int i=0; i<10; i++)
{
a[i] += 1;
b[i] += 2;
c[i] += 3;
}
// √
for (int i=0; i<10; i++)
a[i] += 1;
for (int i=0; i<10; i++)
b[i] += 2;
for (int i=0; i<10; i++)
c[i] += 3;
// 其實上面這樣寫的目的是為了在一個循環裡面可以連續尋址,加載進NEON寄存器,
// 是以下面這種寫法就不如合起來寫
struct rgb {char r; char g; char b};
struct rgb out[10];
for (...)
out[i].r = ...;
for (...)
out[i].g = ...;
for (...)
out[i].b = ...;
- 在循環中避免使用break和if
- 盡量讓循環次數時2的幂
- 盡量讓編譯器能知道要循環多少次,比如說
// 編譯器對循環次數一無所知
void func(int n)
{
for (int i=0; i<n; i++)
{
...
}
}
// 編譯器知道循環次數是4的倍數
void func(int n)
{
for (int i=0; i<n & (~3); i++)
// or for (int i=0; i<n*4; i++)
{
...
}
}
- 循環内的函數調用盡量使用inline
- 多用數組來indexing,而不要用指針
- 不要用Indirect Addressing(這個我也沒懂,反正應該不是寄存器的簡介尋址)
- 使用restrict關鍵字讓編譯器知道code中沒有使用重疊的記憶體區域
- 關于結構體,NPG中說了兩種不好的寫法,
// 有的同學會為了對齊到32-bit在下面結構體的最後加上一個not_used的字段,
// 但是在循環中沒有用到not_used字段,會打斷第1點中說的NEON連續尋址,是以編譯器也不會做auto-vectorization
struct aligned_pixel
{
char r;
char g;
char b;
char not_used; /* Padding used to keep r aligned to a 32-bit word */
}screen[10];
// 這個就更不用說了, 資料類型都不一樣,編譯器會說我好難。
// 不缺記憶體的情況下,可以将結構體中的資料類型都對齊到最寬的資料類型
struct pixel
{
char r;
short g; /* Green channel contains more information */
char b;
}screen[10];
NPG中在好多地方分散說了很多進行vectorization的代碼注意要點,我這邊總結了我認為比較重要的部分,但難免有些細節遺漏,如果有興趣,可以深入看看NPG第2章。其實總結起來,無非是如果你的code你自己要做vectorization都覺得困難,那就别指望編譯器了。
利用gcc進行vectorization
編譯使用選項——
gcc/g++ xxx.c -o target -ftree-vectorize -mfpu=neon -mcpu=cortex-aN
其中,
-
表示讓編譯器進行vectorization。在我的實際使用中,感覺沒啥效果,要直接-ftree-vectorize
才能vectorization-O3
-
指定FPU。對于我使用的cortex-a7,NPG推薦使用-mfpu
(不過隻用-mfpu=neon-vfpv4
好像也能用…)neon
處理器 | -mfpu |
---|---|
cortex-a5 | neon-fp16 |
cortex-a7 | neon-vfpv4 |
cortex-a8 | neon |
cortex-a9 | neon-fp16 |
cortex-a15 | neon-vfpv4 |
-
指定cpu型号。-mcp