1、前言
? 最近在學習NEON指令集,主要學習"單指令流,多數據流"的編程方式,在這之前主要是針對cuda編程進行學習。最近的一部分工作轉移到了arm主板上,接觸到了 ncnn這個開源項目,不得不佩服nihui的強大。在學習ncnn的過程中了解到,arm下的模型加速主要用的技術就有NEON.因此,本著期待有一天能夠熟讀ncnn源碼為目標,從0開始學習NEON!.
? 關于NEON的介紹,這里不再贅述,網上有很多詳細的文章,我是從下面這幾篇文章入門的。
ARM SIMD 指令集:NEON 簡介,CPU 優化技術-NEON 指令介紹,ARM NEON指令集總結, 本系列的文章主要是記錄從0學習NEON中的一些知識點,便于往后復習,或者對剛學習NEON的同學有所幫助,如有不對的地方,還望指正。我的學習環境是一塊國產主板,沒有板子的同學可以使用NEON_2_SSE.h頭文件在x86平臺上進行仿真。
? 由于本人是CV領域的,后面的學習都會在圖像、深度學習部署方向進行,后續文章中還會匯總一些高質量的博客,便于參照學習,文章也許并不是循序漸進的,是在網上看到什么學習資料,就會耕畜相應的鏈接,并在本地進行學習或者復現。
2、學習記錄
2.1、第一個例子
#include <stdio.h>
#include <stdlib.h>
// 包含NEON頭文件
#include <arm_neon.h>
#include <math.h>int main(){unsigned char a[8] = {0,1,2,3,4,5,6,7};unsigned char b[8] = {8,9,10,11,12,13,14,15};unsigned char c[8];// uint8x8_t uint8類型的數據,連續讀取8個數據,uint8x8_t rega, regb, regc;// 從內存中讀取8個8位數據到寄存器, vld1q_u8則表示從內存中讀取16個8位數據到內存中// 'q'指明操作寄存器寬度,為'q'時操作QWORD, 為128位,未指明時操作寄存器為DWORD,為64位;rega = vld1_u8(&a[0]); regb = vld1_u8(&b[0]);// 向量加法,將兩個寄存器中的值相加,使用一條指令就可以并行實現8個加法,體現了單指令多數據的思想。regc = vadd_u8(rega, regb); // 將寄存器中的值寫回到內存中vst1_u8(&c[0], regc);for(int i=0;i<8;i++){printf("%d ", c[i]);}return 0;
}
通過上面的例子,NEON的基本步驟和cuda是一樣的,先將數據搬運到寄存器(顯存),進行并行計算,最后再把結果寫回到內存中。
2.2、第二個例子
#include <stdio.h>
#include <stdlib.h>
#include <arm_neon.h>
#include <math.h>
#include <iostream>
using namespace std;
int main(){// 1、直接在q寄存器中初始化4個類型為float的值。float32x4_t arr = vdupq_n_f32(1.0); //0初始化 arr = {1.0, 1.0, 1.0, 1.0}// 2、累加和float arr[4] = {1.0, 2.0, 3.0, 4.0};float32x4_t arr = vld1q_f32(arr); // 將數據加載到寄存器float ans = vaddvq_f32(arr); // ans = 1+2+3+4 = 10return 0;
}
2.3、第三個例子
第三個例子來自于https://blog.csdn.net/weixin_40162153/article/details/129109774?spm=1001.2014.3001.5502,
在opencv以RGB的格式讀取一張圖片時,其在內存中的排列為 RGBRGBRGB…,有時候,我們想把每個通道值單獨提取出來進行直方圖均衡化或者其他操作,在c++中,我們可以得到如下的代碼。
void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {for (int i=0; i < len_color; i++) {r[i] = rgb[3*i];g[i] = rgb[3*i+1];b[i] = rgb[3*i+2];}
}
而通過NEON加速,可以通過如下的代碼進行。
void rgb_deinterleave_neon(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {int num8x16 = len_color / 16; //使用128位的q寄存器進行操作,需要操作的次數uint8x16x3_t intlv_rgb; // 數據格式為 uint8類型的數據,16*3組。每次使用3個q寄存器.每個寄存器分別儲存R、G、B值。for (int i=0; i < num8x16; i++) {// rgb+3*16*i step,每處理一次需要往后+多少個位置,intlv_rgb = vld3q_u8(rgb+3*16*i); // vld3q_u8,從起始點位置 交織加載 16個RGB數據vst1q_u8(r+16*i, intlv_rgb.val[0]); // 將16個R數據寫回到內存vst1q_u8(g+16*i, intlv_rgb.val[1]); // 將16個G數據協回到內存vst1q_u8(b+16*i, intlv_rgb.val[2]); // 將16個B數據協回到內存}
3、總結
通過閱讀幾篇入門文章,初步了解了NEON的加速機制,主要是如何利用好d、q寄存器。使得加載和處理數據的時候后能夠占滿寄存器,。后續應該還會遇到向量化剩余部分(leftovers)處理進行學習。
?