NEON介紹
在移動平臺上進行一些複雜算法的開發,一般需要用到指令集來進行加速。NEON 技術是 ARM Cortex™-A 系列處理器的 128 位 SIMD(單指令,多數據)架構擴展,專門針對大規模並行運算設計的,旨在爲消費性多媒體應用程序提供靈活、強大的加速功能,從而顯著改善用戶體驗。
其本質上使用的是128位NEON SIMD寄存器,這意味着如果操作32位浮點數,可同時操作4個(變量可定義:float32x4_t);如果操作 16 位整數(short),可同時操作 8 個(變量可定義:int16x8_t);而如果操作 8 位整數,則可同時操作 16 個(變量可定義:int8x16_t)。
ARMv7 NEON 指令集架構具有 16 個 128 位的向量寄存器,命名爲 q0~q15。這 16 個寄存器又可以拆分成 32 個 64 位寄存器,命名爲 d0~d31。其中qn和d2n,d2n+1是一樣的,故使用匯編編寫代碼時要注意避免產生寄存器覆蓋。如下圖所示:
NEON數據類型
NEON的數據類型如下圖:
neon的數據類型float32x4_t 可以理解爲vector< float32 > (4),同理typexN_t即爲vector< type>(N)。在NEON編程中,對單個數據的操作可以擴展爲對寄存器,也即同一類型元素矢量的操作,因此大大減少了操作次數。
NEON中指令分爲正常指令、寬指令、窄指令、飽和指令、長指令這幾類:
正常指令:數據寬度不變
//操作數爲int16x4_t,結果數爲int16x4_t
int16x4_t vadd_s16 (int16x4_t __a, int16x4_t __b);
長指令:源操作數寬度相同 結果寬度擴展 L標記
//操作數爲int16x4_t,結果數爲int32x4_t,vaddl_s16中l標誌指令爲長指令
int32x4_t vaddl_s16 (int16x4_t __a, int16x4_t __b);
寬指令:源操作數寬度不同 結果寬度對齊 W標記
//操作數一個爲uint32x4_t,一個爲uint16x4_t,結果對齊uint32x4_t,w標誌指令爲寬指令
uint32x4_t vaddw_u16 (uint32x4_t __a, uint16x4_t __b);
窄指令:源操作數寬度相同 結果寬度變窄 N標記
//操作數爲uint32x4_t,結果數爲uint16x4_t,n標誌指令爲窄指令
uint16x4_t vaddhn_u32 (uint32x4_t __a, uint32x4_t __b);
飽和指令:結果溢出就是飽和指令 Q標記
NEON官方示例及詳解
通過一個示例來解釋如何利用NEON內置函數來加速實現統計一個數組內的元素之和。
#include <iostream>
using namespace std;
float sum_array(float *arr, int len)
{
if(NULL == arr || len < 1)
{
cout<<"input error\n";
return 0;
}
float sum(0.0);
for(int i=0; i<len; ++i)
{
sum += *arr++;
}
return sum;
}
對於長度爲N的數組,上述算法的時間複雜度爲O(N)。
採用NEON函數進行加速:
#include <iostream>
#include <arm_neon.h> //需包含的頭文件
using namespace std;
float sum_array(float *arr, int len)
{
if(NULL == arr || len < 1)
{
cout<<"input error\n";
return 0;
}
int dim4 = len >> 2; // 數組長度除4整數
int left4 = len & 3; // 數組長度除4餘數
float32x4_t sum_vec = vdupq_n_f32(0.0);//定義用於暫存累加結果的寄存器且初始化爲0
for (; dim4>0; dim4--, arr+=4) //每次同時訪問4個數組元素
{
float32x4_t data_vec = vld1q_f32(arr); //依次取4個元素存入寄存器vec
sum_vec = vaddq_f32(sum_vec, data_vec);//ri = ai + bi 計算兩組寄存器對應元素之和並存放到相應結果
}
//將累加結果寄存器中的所有元素相加得到最終累加值
float sum = vgetq_lane_f32(sum_vec, 0)+vgetq_lane_f32(sum_vec, 1)+vgetq_lane_f32(sum_vec, 2)+vgetq_lane_f32(sum_vec, 3);
for (; left4>0; left4--, arr++)
sum += (*arr) ; //對於剩下的少於4的數字,依次計算累加即可
return sum;
}
上述算法的時間複雜度爲O(N/4),原因在於我們每次往寄存器加載4個float值,然後同時相加。相當於原來需要N次加法操作而現在只需要N/4即可。如果使用更多的寄存器,則可以完成更高倍數的加速。
上述用到的幾個NEON指令解釋爲:
float32x4_t vdupq_n_f32(float32_t val):將val複製四份放入返回的寄存器中。
float32x4_t vld1q_f32(float32_t const * ptr):從地址ptr依次向後加載四個元素放入返回的寄存器中。
float32x4_t vaddq_f32(float32x4_t a, float32x4_t b):返回a+b的值,向量運算,四個值同時相加。
float32_t vgetq_lane_f32(float32x4_t v, const int lane):返回v中某一個lane的值
除以上的操作外,NEON還支持很多的操作,如矢量相減、矢量相乘、矢量乘加、矢量類型轉換等等。
NEON手冊
以下鏈接爲NEON內置函數的手冊,當需要用到某些NEON操作時,可以通過手冊查看使用方法。
NEON內置函數詳細手冊