天天看點

NEON Programmer's Guide(2) —— 讓編譯器幫你實作NEON加速簡介NEON庫讓編譯器将代碼編譯成NEON指令

本文内容基于《NEON Prgrammer’s Guide》(下稱NPG)第二章Compiling NEON Instruction,所有編譯指令都基于gcc-arm

簡介

讓你的程式使用arm核心的NEON unit進行并行計算,提升嵌入式程式性能,一般有3種方法:

  1. 手撸NEON代碼,無論是彙編還是Intrinsics
  2. 使用NEON庫
  3. 讓編譯器将代碼編譯成NEON指令

第1項是NPG的正題,以後再說,這一章還是說一些簡單的,比如第2項和第3項。

NEON庫

NPG中推薦了幾個開源的利用NEON進行優化的函數庫(不過我看的是2013年的NPG,是不是後來又出了其他庫就不知道了),有需要百度庫名即可:

  1. Ne10,将NEON指令封裝成了C語言函數,實作了一些基礎數學和dsp運算,估計以後會用,用的時候再介紹
  2. OpenMAX,open media acceleraion,一個多媒體加速庫,有為ARM實作的NEON加速版本
  3. ffmpeg,搞音視訊的應該比較熟悉,一個音視訊編解碼庫。
  4. Eigen3,一個線性代數的C++庫,做圖像或者機器人的朋友應該常用
  5. Pixman,一個2D圖形圖像函數庫
  6. x264,一個H.264的圖像編碼庫
  7. Math-noen,另外一個數學操作函數庫
  8. 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,

  1. 讓循環變得盡可能的簡單和短,比如說,
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 = ...;
           
  1. 在循環中避免使用break和if
  2. 盡量讓循環次數時2的幂
  3. 盡量讓編譯器能知道要循環多少次,比如說
// 編譯器對循環次數一無所知
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++)
	{
		...
	}
}
           
  1. 循環内的函數調用盡量使用inline
  2. 多用數組來indexing,而不要用指針
  3. 不要用Indirect Addressing(這個我也沒懂,反正應該不是寄存器的簡介尋址)
  4. 使用restrict關鍵字讓編譯器知道code中沒有使用重疊的記憶體區域
  5. 關于結構體,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

其中,

  1. -ftree-vectorize

    表示讓編譯器進行vectorization。在我的實際使用中,感覺沒啥效果,要直接

    -O3

    才能vectorization
  2. -mfpu

    指定FPU。對于我使用的cortex-a7,NPG推薦使用

    -mfpu=neon-vfpv4

    (不過隻用

    neon

    好像也能用…)
處理器 -mfpu
cortex-a5 neon-fp16
cortex-a7 neon-vfpv4
cortex-a8 neon
cortex-a9 neon-fp16
cortex-a15 neon-vfpv4
  1. -mcp

    指定cpu型号。