ARM平臺下NEON使用方法詳解

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內置函數詳細手冊

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