嵌入式程序優化(2)-neon內建函數講解


1. neon intrinsics介紹

neon intrinsics 是官方提供的 neon內建函數,使開發者不再需要手動使用內嵌匯編也能夠使用 neon指令 來優化程序。本來着重講解此類內建函數的使用方法及簡單的代碼實例
本文也可用作neon內建函數的快速查找表

PS:本文默認讀者們已經熟悉了neon指令的基本情況

2. neon數據類型

  • 普通向量:分64bit向量和128bit向量
  • 數組向量: 格式如:type{n}x{m}_{z}, n 為向量元素的寬度, m 為向量元素的個數, z 為向量的個數。一般用於解交織操作, 這種類型一般為一個結構體 請自行查閱資料(解交織)
    比如int8x8x3_t類型指的就是該變量中含有 3 個int8x8_t的向量,分別對應成員:int8x8x3_t.val[0], int8x8x3_t.val[1], int8x8x3_t.val[2]

3. neon內建函數

在講解之前,這里先說明一下內建函數的命名套路,讓讀者們可以更快的理解內建函數

內建函數的命名規則通常如下:

  • vop:指的是neon操作
  • {n}or{lane}:其中兩者是互斥的,n 代表的是支持輸入標量,lane支持選擇向量中的一個元素作為輸入
  • {datatype}:代表處理元素的類型

注意:
1. 帶n的函數都含有立即數,此時輸入參數不能為變量,且該立即數有范圍限制, 具體請查看手冊
2. 帶qd操作的函數一般只支持32x4跟64x2類型的輸入和輸出

筆者通過閱讀 neon手冊 並結合自己的理解將 neon內建函數 分為以下幾個部分

  • 常用操作
  • 裝載和存儲操作
  • 轉換類操作
  • 加減法類操作
  • 乘法類操作
  • 數據處理類操作
  • 偏移類操作
  • 排列類操作

3.1. 常用操作

1. vcreate 創建一個64bit的向量,輸入為一個64位的標量

1 uint64_t vcreate_1;
2 int8x8_t vcreate_result;
3 vcreate_result = vcreate_s8(vcreate_1);

2. vdup_n 將標量賦值到向量每一個元素中, vdupq_n支持q寄存器操作, 下面的vmov_n作用同vdup_n

1 int8_t vdup_1;
2 int8x8_t vdup_result;
3 vdup_result = vdup_n_s8(vdup_1);

3. vdup_lane 將輸入向量中的每一個元素復制到目標向量的所有元素中

1 int8x8_t vdup_lane_1;
2 int8x8_t vdup_lane_reuslt;
3 int index = 1;//下標范圍從0-7
4 vdup_lane_reuslt = vdup_lane_s8(vdup_lane_1, index);

3.2. 裝載和存儲操作

1. vldn 將內存中的數據按n路解交織存入向量中, 比如 按2路解交織, 有數據data[0], data[1], data[2], data[3], 那么存放時data[0],data[2] 存放在vld2_result.val[0]中, data[1],data[3] 存放在vld2_result.val[1]中

1 /* 
2     vldn注意事項, 只有n為 1 時結果才能定義為type{n}x{m}_t類型, 即普通向量, 
3     當 n 大於 1 時,此時的載入操作都是解交織的, 所以需要定義為數組向量 
4 */
5 int8_t vldn_data[4] = {0, 1, 2, 3};
6 int8x8x2_t vld2_result;
7 vld2_result = vld2(vldn_data);

2. vldn_lane 將內存中的數據按n路解交織存入到向量的某一個元素中

1 int8_t vldn_lane_data[4] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
2 int8x8x3_t vld3_lane_result;
3 int8x8x3_t vld3_lane_1;
4 int vld3_lane_index = 4;
5 vld3_lane_result=vld3_lane_s8(vldn_lane_data,vld3_lane_1,vld3_lane_index);

3. 將vldn_lane_data按順序取出 3 個元素存入向量數組vld3_lane_1中的下標為vld3_lane_rindex的元素, reslut是未加載的元素將按原樣返回的結果。

4. vldn_dup, 將內存中的數據拷貝到每一個向量中

1 int8_t vldn_dup_data[4] = {0, 1, 2, 3};
2 int8x8x4_t vld4_dup_result;
3 vldn_lane_data = vld4_dup_s8(vldn_dup_data);

5. vstn 將向量的值按 n 路交織存放到內存中, 比如有int8x8x3_t的數組向量, 那么將按照int8x8x3_t.val[0][0], int8x8x3_t.val[1][0], int8x8x3_t.val[2][0], int8x8x3_t.val[0][1], int8x8x3_t.val[1][1], int8x8x3_t.val[2][1]這樣的順序存入內存

int8x8x3_t vst3_1;
int8_t vst3_data[6] = {0};
vst3_s8(vst3_data, vst3_1);

6. vstn_lane, 將向量中某個通道的元素存入內存中, 比如int8x8x4_t

1     /* 除了跟vldn一樣需要注意類型的定義外,還需要知道vstn是無返回的, 即為void */
2     int8x8x4_t vst4_1;
3     int8_t vst4_data[6] = {0};
4     vst3_lane_s8(vst4_data, vst4_1, 2);  

7. vset_lane 設置向量中某個通道的元素的值, 並將新的向量返回到結果中

1     int8x8_t vset_lane_result;
2     int8x8_t vset_lane_1;
3     int8_t new_val;
4     int vset_index = 3;
5     /*
6         將向量vset_lane_1中下標為vset_index的值設置為new_val, 
7         並將設置了新值的新向量返回到vset_lane_result中
8     */
9     vset_lane_result = vset_lane_s8(new_val, vset_lane_1, vset_index);

8. vget_lane 獲取向量中某個通道的值, 並將新的向量返回到結果中

1     int8x8_t vget_lane_1;
2     int8_t vget_lane_result;
3     int vget_index;
4     /* 將向量vget_lane_1中下標為vget_index的值賦給vget_lane_result */
5     vget_lane_result = vget_lane_s8(vget_lane_1, vget_index);

3.3. 轉換類操作

1. vreinterpret_type 向量轉換, v指向量 re指再一次, interpret是解釋,也就是將向量再進行一次解釋, 如果后面加 q, 則表示在 q寄存器 中進行操作

    uint16x4_t vector_uint16x4;
    int16x4_t vector_int16x4;
    vector_uint16x4 = vreinterpret_u16_s16(vector_int16x4);

2. vcombine_type 向量連接,將 2 個 64bit的 d寄存器向量 連接為一個 128bit 的 q寄存器向量,被連接的2個向量必須是相同類型的向量, 格式為 vcombine_type

    int16x4_t top_int16x4;
    int16x4_t bottom_int16x4;
    int16x8_t result;
    result = vcombine_s16(bottom_int16x4, top_int16x4);

3./vget_high_type 獲取 q寄存器向量高部分內容到 d寄存器向量中, vget_low_type同理

    int16x4_t vgh_int16x4;
    int16x8_t vgh_int16x8;
    vgh_int16x4 = vget_high_s16(vgh_int16x8);

3.4. 加減法類操作

1. 加法操作 vadd_type常規加法,vaddl_type支撐長整型操作, vaddw_type支持寬整型操作, vhadd_type將結果減半, vrhadd_type將結果減半后舍入(因為減半后會帶入小數)

2. vaddq_type, 和vadd_type類似,只是使用的寄存器不同. vaddhn_type支持窄型計算,將四字節加法轉換為雙字節結果, 該計算會返回四字節的上半部分, vraddhn_type支持結果舍入

3. 減法同理

    int16x4_t add_1;
    int16x4_t add_2;
    int16x4_t add_result;
    add_result = vadd_s16(add_1, add_2);

3.5. 乘法類操作

注意:在做neon乘法指令的時候會有大約2個clock的阻塞時間,如果你要立即使用乘法的結果,則就會阻塞在這里,在寫neon指令的時候需要特別注意。乘法的結果不能立即使用,可以將一些其他的操作插入到乘法后面而不會有時間的消耗。

1. vmul為常規乘法, vmull支持長整型操作, vmla支持結果累加, vmlal支持長整型運算, vmls支持結果累減(同vmla_type一樣), vmlsl是vmls支持長整型操作的變體

2. vqdmulh, 將相乘結果加倍,然后返回結果的高半部分並截斷高半部分, 帶q表示飽和操作, vqdmulhq支持q寄存器操作, vqrdmulh返回結果不截斷而是進行舍入

3. vqdmlal, 支持長整型操作, 將第三個向量和第二個向量相乘,並與第一個向量累加返回結果,結果支持飽和操作, vqdmlsl同, 只是改為相乘結果累減

4. vqdmull, 第一個q為飽和操作, d代表結果加倍, 最后的l代表支持長整型操作

5. vmla_lane_type, lane代表支持下標操作, 參數n是下標,表明第三個向量中,取出下標為n的標量, 並將其與向量二中的每一個向量相乘,將結果和向量一累加

6. vmlal_lan_type支持長整型操作, vqdmlal_lan_type支持飽和、結果加倍和長整型操作, vmls_lane_type系列同理

7. vmul_n_type 即使用標量相乘和向量中的每一個元素相乘, 其余函數類似僅支持的操作不同

    int16x4_t mul_1;
    int16x4_t mul_2;
    int16x4_t mul_3;
    int16x4_t mul_result;
    mul_result = vmul_s16(mul_1, mul_2);
    /* mul_2和mul_3相乘的結果和mul_1的向量相加 */
    mul_result = vmla_s16(mul_1, mul_2, mul_3);

3.6. 數據處理類操作

1. vpadd, 這里p是pair的意思,也就是一對,意思就是向量內部相鄰的一對元素進行相加, 也就是4個元素的向量輸出2個元素,所以這里需要輸入 2 個向量,比如2個輸入向量都是4個元素,那么每個輸入向量輸出2個元素,那么2個輸入向量最終輸出的是4個元素

2. vpaddl跟vpadd相同,但支持長整型操作

3. vpadal, 后面的a表示累加,比如第二個向量為8x8,那么進行padd操作后輸出8x4,l代表8x4的結果寬度拓展為2倍變成16x4,a表示將16x4的結果與第一個向量相加

    int16x4_t result;
    int8x8_t vpaddl_1;
    result = vpaddl_s8(vpaddl_1);

4. vpmax, p代表相鄰向量, 將向量內部的相鄰元素進行比較, 輸出較大的元素, vpmin同理

    int16x4_t result;
    int16x4_t vpmax_1;
    int16x4_t vpmax_2;
    result = vpaddl_s8(vpmax_1, vpmax_2);

5. vpmax, p代表相鄰向量, 將向量內部的相鄰元素進行比較, 輸出較大的元素, vpmin同理

    int16x4_t result;
    int16x4_t vpmax_1;
    int16x4_t vpmax_2;
    result = vpaddl_s8(vpmax_1, vpmax_2);  

6. vabd, 向量對應的元素絕對值相加, vabdq支持q寄存器,vabdl支持長整型操作

7. vada跟vabd一樣但支持結果累加, vabal支持累加和長整型操作

    int16x4_t result;
    int16x4_t vabd_1;
    int16x4_t vabd_2;    
    result = vabd_s16(vpmax_1, vpmax_2);  

8. vmax, 向量對應的元素進行比較,輸出較大的元素, vmin同理

    int16x4_t vmax_result;
    int16x4_t vmax_1;
    int16x4_t vmax_2;    
    resulvmax_resultt = vabd_s16(vmax_1, vmax_2);  

9. vabs向量內部元素絕對值化, vqabs支持飽和操作

    int16x4_t vabs_result;
    int16x4_t vabs_1;
    vabs_result = vabs_s16(vabs_1);

10. vneg, 向量內部元素取反, vqneg支持飽和操作

    int16x4_t vneg_result;
    int16x4_t vneg_1;
    vneg_result = vneg_s16(vneg_1);

11. vcls對向量內的元素進行以下操作, 從最高位開始的連續的多個bit進行計數, 如果某一個bit和最高位相同,則計數加1, 計數時不算最高位. 比如0b10110101為0, 0b11110101為3

12. vclz從最高位開始對連續bit進行計數,如果bit為0, 則計數加1,計數時最高位參與計算

13. vcnt計算向量內部每個元素有多少bit位1

    int16x4_t vcls_result;
    int16x4_t vcls_1;
    int16x4_t vcls_2;
    vcls_result = vneg_s16(vcls_1, vcls_2);

14. vrecpe對向量內部每個元素求近似導數, 近支持32x2類型的向量

    uint32x2_t vrecpe_result;
    uint32x2_t vrecpe_1;
    vrecpe_result = vrecpe_u32(vrecpe_1); 

15. vrecps對向量內部的元素進行newton-raphson計算求出倒數

  float32x2_t vrecps_result;
  float32x2_t vrecps_1;
  float32x2_t vrecps_2;
  vrecps_result = vrecpe_f32(vrecps_1, vrecps_2);    

16. vrsqrte對向量內部的元素求平方根倒數

    float32x2_t vrsqrte_result;
    float32x2_t vrsqrte_1;
    vrsqrte_result = vrecpe_f32(vrsqrte_1); 

17. vrsqrts對向量內部的元素進行newton-raphson計算求出倒數

    float32x2_t vrsqrts_result;
    float32x2_t vrsqrts_1;
    float32x2_t vrsqrts_2;
    vrecps_result = vrecpe_f32(vrecps_1, vrecps_2);  

18. vmovn 將q寄存器中的高部分賦值到結果中, 可以看出該操作是窄型操作, vqmovn支持飽和操作, vqmovun中的u是指可以將符號類型的輸入向量計算出無符號類型的結果

  int8x8_t vmovn_result;
  int16x8_t vmovn_1;  
  vmovn_result = vmovn_s16(vmovn_1);

  uint8x8_t vqmovun_result;
  int16x8_t vqmovun_1; 
  vqmovun_result = vqmovun_s16(vmovn_1);

19. vmovl 將d寄存器中的元素寬度擴展為原來的2倍,並將結果存入q寄存器中, 可以看出該操作是長整型型操作

    int16x8_t vmovl_result;
    int8x8_t vmovl_1;
    vmovl_result = vmovn_s8(vmovl_1); 

3.7. 偏移類操作

1. vshl 將輸入向量(第一個向量)按偏移向量(第二個向量)中的每一個元素的值向左進行偏移, 每個元素中從左側移出的位將丟失, 偏移向量按照lsb排序並且是有符號的,如果偏移向量中的元素為負值, 那么將向右進行偏移

2. vqshl 支持飽和操作

3. vrshl 支持位移后對數值進行舍入取整數

4. vqrshl 同時支持舍入和飽和操作

5. vshl_n 將輸入的標量作為偏移量, 對向量中的每一個元素進行坐移

6. vqshl_n 作用同vshl_n, 支持飽和操作

7. vqshlu_n 作用同vqshl_n, 支持將有符號數轉換為無符號數 (u代表可以將輸入的有符號數在進行運算后輸出為無操作數)

8. vshll_n 作用同vshl_n, 支持長整型操作

    int8x8_t vshl_input;
    int8x8_t vshl_offset;
    int8x8_t vshl_result;
    vshl_result = vshl_s8(vshl_input, vshl_offset);

9. vshr_n 將輸入的標量作為偏移量, 對向量中的每一個元素進行右移

10. vrshr_n 作用同vshr_n,支持將偏移后的元素的值進行舍入

11. vshrn_n 作用同vshr_n, 支持窄型操作,即將q寄存器類型的向量轉換為d寄存器類型的向量

12. vqshrun_n 是在vshr_n的基礎上, 支持飽和, 有符號轉無符號以及窄型操作

13. vqrshrun_n 在vqshrun_n基礎上支持將結果進行舍入

    int8_t offset = 2;
    int8x8_t vshr_n_input;
    int8x8_t vshr_n_result;
    vshr_n_result = vshr_n_s8(vshr_n_input, offset);

14. vsra_n a表示累加, r表示右移, n表示立即數, 將 2 個輸入向量的每一個元素都按照立即數n進行右移, 再將右移后的結果累加到結果向量中

15. vrsra_n 支持將累加后的結果進行舍入

    int8x8_t vsra_n_input_1;
    int8x8_t vsra_n_input_2;
    int8x8_t vsra_n_result;
    int offest = 2;
    vsra_n_result = vsra_n_s8(vsra_n_input_1, vsra_n_input_2, offset);

16. vsri_n 第一個輸入向量可以保存原來的目標向量。將第二個向量的每個元素按照立即數n進行右移, 將右移后的數據與目標向量中的數據進行插入合並, 即右移后的向量元素插入時不會影響目標寄存器中元素的最高n個有效位

17. vsli_n 第一個輸入向量可以保存原來的目標向量。將第二個向量的每個元素按照立即數n進行左移, 將左移后的數據與目標向量中的數據進行插入合並, 即左移后的向量元素插入時不會影響目標寄存器中元素的最低n個有效位

  int8x8_t vsri_n_result;
  int8x8_t vsri_n_input1;
  int8x8_t vsri_n_input2;
  int offset = 2;
  /*
  此處立即數n為2, 
  則有(vsri_n_result[0] & 0b11000000) | (vsri_n_input2[0] & 0b00111111)
  */
  vsri_n_result = vsli_n_s8(vsri_n_input1, vsri_n_input2, offset);

18. vceq 比較第一個輸入向量與第二輸入向量,然后將比較結果放在對應結果向量的對應元素,相等為 1 , 不相等為 0

19. vcge 比較第一個輸入向量與第二輸入向量,然后將比較結果放在對應結果向量的對應元素,如果第一個向量元素大於等於第二個向量,則結果為 1, 否則為 0

20. vcle 比較第一個輸入向量與第二輸入向量,然后將比較結果放在對應結果向量的對應元素,如果第一個向量元素小於等於第二個向量,則結果為 1, 否則為 0

21. vcgt 比較第一個輸入向量與第二輸入向量,然后將比較結果放在對應結果向量的對應元素,如果第一個向量元素大於第二個向量,則結果為 1, 否則為 0

22. vclt 比較第一個輸入向量與第二輸入向量,然后將比較結果放在對應結果向量的對應元素,如果第一個向量元素小於第二個向量,則結果為 1, 否則為 0

23. vcage 與vcge類似,但比較的值是向量元素的絕對值

24. vcale 與vcle類似,但比較的值是向量元素的絕對值

25. vcagt 與vcgt類似,但比較的值是向量元素的絕對值

26. vcalt 與vclt類似,但比較的值是向量元素的絕對值

    int8x8_t vceq_input1;
    int8x8_t vceq_input2;
    int8x8_t vceq_result;
    vceq_result = vceq_s8(vceq_input1, vceq_input2);

27. vtst 按位與運算邏輯, 將兩個向量的元素按位進行與邏輯運算,如果對應元素的運算結果不為 0, 則目的向量中的對應元素置為 全1, 否則置為 全0

    int8x8_t vtst_input1;
    int8x8_t vtst_input2;
    int8x8_t vtst_result;
    vtst_result = vtst_s8(vtst_input1, vtst_input2);

30. vmvn對輸入向量中的每個元素進行按位反轉

    int8x8_t vmvn_input;
    int8x8_t vmvn_result;
    vmvn_result = vmvn_s8(vmvn_input);

31. vand在輸入向量的相應元素之間執行按位與運算

32. vorr在輸入向量的相應元素之間執行按位或運算

33. veor在輸入向量的相應元素之間執行按位異或運算

34. vbic對第一個向量中的元素進行按位清除。 要清除的位是在第二向量的元素中設置的位

35. vorn在第一個向量的元素與第二個向量的元素的補碼之間執行按位“或”運算。

    int8x8_t vand_input1;
    int8x8_t vand_input2;
    int8x8_t vand_result;
    vand_result = vand_s8(vand_input1, vand_input2);

**36. **vbsl 有 3 個輸入向量,如果第三個向量中的一個元素中的某一個bit為0, 則選擇第一個向量中的對應元素的對應bit到目標向量中的對應元素對應bit中, 如果為0, 則從第二個向量中選擇的對應元素的對應bit賦給目標向量中的對應元素的對應bit

    int8x8_t vbsl_input1;
    int8x8_t vbsl_input2;
    int8x8_t vbsl_input3;
    int8x8_t vbsl_result;
    vbsl_result = vand_s8(vbsl_input1, vbsl_input1, vbsl_input2, vbsl_input3);

3.8. 排列類操作

1. vext 從第二個元素的低端提取n 個元素放在目標向量的高端, 在第一個元素的高端提取剩余的元素放在目標向量中的低端

    int n = 5;
    int8x8_t vext_input1;
    int8x8_t vext_input2;
    int8x8_t vext_result;
    vext_result = vext_s8(vext_input1, vext_input2, n);

2. vtbln 該指令用於查表賦值, 一共有 2 個輸入向量, 第二個輸入向量的元素是作為索引, 第一個輸入向量作為查找表, 該指令會在使用索引然后在查找表中找到指定索引的元素,然后將找到的元素賦值給結果向量中的元素(與索引所在的元素對應, 假設索引是在第一個元素,則存放在結果向量的第一個元素)
n范圍為[0, 4] 其表示的是使用的d寄存器數量,當n大於1時, 第一個參數使用的是數組向量, 如果索引超過查找表范圍, 則返回0

3. vtbln:與 vtbln 一樣, 但在超出范圍時結果向量中的元素不會變為0, 而是保持不變

  char table[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
  char index[8] = {0, 1, 2, 3, 4, 5, 6, 7};
  int8x8x2_t vtbl2_input1;
  int8x8_t vtbl2_input2;
  int8x8_t vtbl2_result;
  vtbl2_input1 = vld2_dup_s8(table);//將查找表解交織裝載到向量中
  vtbl2_input2 = vld1_dup_s8(index);
  vtbl2_result = vtbl2_s8(vtbl2_input1, vtbl2_input2);

4. vrev64 以32bit為一個單位, 反轉向量的一個單位內指定類型元素的順序, 並將結果放入相應的目標向量中. 支持的指定類型元素有8bit, 16bit和32bit. 注意: 反轉的是元素在向量內部的順序,不是元素bit的順序

5. vrev32與vrev64類似, 但只支持8位和16位

6. vrev16與vrev64類似, 但只支持8位

  int8x8_t vrev64_input;
  int8x8_t vrev64_result;
  vrev64_result = vrev64_s8(vrev64_input);

7. vtrn將其輸入向量的元素視為2 x 2矩陣的元素,並將其轉置。 本質上,它將第一個輸入向量中的奇數索引的元素與第二個輸入向量中的偶數索引的元素交換。

 
trn示意.png
    int8x8_t vtrn_input1;
    int8x8_t vtrn_input2;
    int8x8x2_t vtrn_result;
    vtrn_result = vtrn_s8(vtrn_input1, vtrn_input2);

8. vzip 將 2 個向量進行交織存放
9. vuzp是 vzip 的方向過程

    int8x8_t vzip_input1;
    int8x8_t vzip_input2;
    int8x8x2_t vzip_result;
    vzip_result = vzip_s8(vzip_input1, vzip_input2);
    return 0;

4. 代碼實例

筆者使用neon內建函數寫了幾個示例代碼,可供各位讀者理解
完整代碼在筆者的github上:https://github.com/wipping/neon,歡迎各位下載

int32_t neon_intrinsics_matrixMul_float4x4(const float* matrix_left, float* matrix_right, float* matrix_result)
{
    if(NULL == matrix_left)
    {
        return -1;
    }   
    if(NULL == matrix_right)
    {
        return -1;
    }    
    if(NULL == matrix_result)
    {
        return -1;
    } 

    int offset = 4;
    int result_addr_offset = 0;
    int i = 0;
    int j = 0;
    float32x4_t matrix_right_row[4];
    float32x4_t matrix_left_row;
    float32x4_t result;

    for(i = 0; i < 4; i++)
    {
        matrix_right_row[i] = vld1q_f32(matrix_right + offset * i);
    }    

    for(j = 0; j < 4; j++)
    {
        matrix_left_row = vld1q_f32(matrix_left + j * offset);
        result = vdupq_n_f32(0);
        for(i = 0; i < 4; i++)
        {
            switch(i)
            {
                case 0:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 0);
                    break;
                case 1:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 1);
                    break;
                case 2:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 0);
                    break;
                case 3:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 1);
                    break;
            }
        }

        vst1q_f32(matrix_result, result);
        matrix_result = matrix_result + offset;
    }  
    return 0;
}

int32_t neon_intrinsics_matrixMul_float3x3(const float* matrix_left, float* matrix_right, float* matrix_result)
{
    if(NULL == matrix_left)
    {
        return -1;
    }   
    if(NULL == matrix_right)
    {
        return -1;
    }    
    if(NULL == matrix_result)
    {
        return -1;
    } 

    int offset = 3;
    int left_addr_offset = 0;
    int result_addr_offset = 0;
    int i = 0;
    int j = 0;
    float right_tmp[4] = {0};
    float left_tmp[4] = {0};
    float result_tmp[4] = {0};
    float32x4_t matrix_right_row[4];
    float32x4_t matrix_left_row;
    float32x4_t result;

    for(i = 0; i < 3; i++)
    {
        bzero(right_tmp, sizeof(float) * 4);
        memcpy(right_tmp, matrix_right + i * offset, sizeof(float) * offset);
        matrix_right_row[i] = vld1q_f32(right_tmp);
    }    

    for(j = 0; j < 3; j++)
    {
        bzero(left_tmp, sizeof(float) * 4);
        memcpy(left_tmp, matrix_left + j * offset, sizeof(float) * offset);
        matrix_left_row = vld1q_f32(left_tmp);
        result = vdupq_n_f32(0);
        for(i = 0; i < 4; i++)
        {
            switch(i)
            {
                case 0:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 0);
                    break;
                case 1:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 1);
                    break;
                case 2:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 0);
                    break;
                case 3:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 1);
                    break;
            }
        }
        bzero(result_tmp, sizeof(float) * 4);
        vst1q_f32(result_tmp, result);
        memcpy(matrix_result, result_tmp, sizeof(float) * offset);
        matrix_result = matrix_result + offset;
    }  
    return 0;
}

int neon_intrinsics_rgb888Tobgr888(uint8_t* image_src, uint8_t* image_dst, uint32_t pixel_num)
{

    uint8_t *src = image_src;
    uint8_t *dst = image_dst;
    int count = pixel_num;
    uint8_t bit_mask = 0xff;
    uint8x16x3_t vsrc;
    uint8x16x3_t vdst;
    uint8x16_t add_tmp;
    uint8x16_t tmp;
    uint8x16_t mask;

    
    /* 注意: 僅支持像素個數8對齊的圖像 */
    mask = vdupq_n_u8(bit_mask);
    add_tmp = vdupq_n_u8(0);
    while (count >= 8) 
    {
        vsrc = vld3q_u8(src);//裝載源數據
        tmp = vdupq_n_u8(0);

        //vswp無內建函數 使用其他方法實現
        tmp = vaddq_u8(vsrc.val[0], add_tmp);

        vsrc.val[0] = vbicq_u8(vsrc.val[0], mask);
        vsrc.val[0] = vaddq_u8(vsrc.val[2], add_tmp);

        vsrc.val[2] = vbicq_u8(vsrc.val[2], mask);
        vsrc.val[2] = vaddq_u8(tmp, add_tmp);

        // /* 循環 */
        vst3q_u8(dst, vsrc);
        dst += 8*3;
        src += 8*3;
        count -= 8;
    }

    return 0;
}

int neon_intrinsics_rgb565Torgb888(uint16_t* image_src, uint8_t* image_dst, uint32_t pixel_num)
{
    uint16_t *src = image_src;
    uint8_t *dst = image_dst;
    int count = pixel_num;
    uint16x8_t vsrc;
    uint8x8x3_t vdst;

    /* 注意: 僅支持像素個數8對齊的圖像 */
    while (count >= 8) 
    {
        vsrc = vld1q_u16(src);//裝載源數據
        /*  
            注意: rgb565轉rgb88因為通道的bit位寬不同,所以需要使用低位進行補償, 具體請自行查閱
            1. 使用vreinterpretq_u8_u16將源向量中的16bit元素轉為8bit元素
            2. 使用vshrq_n將8bit元素作移 5 位, 這樣即可將位於高位的紅色數據從5bit轉為8bi
            3. 將vreinterpretq_u16_u8 將8bit數據轉換為16bit, 此時高8bit為紅色通道數據
            4. 然后在使用vshrn_n_u16向右移動8個bit, 此時低8bit為紅色數據, 並因為窄型數據操作的原因, 所以可以將16bit數據移動並轉換為8bit
        */
        vdst.val[0] = vshrn_n_u16(vreinterpretq_u16_u8(vshrq_n_u8(vreinterpretq_u8_u16(vsrc), 3)), 5);
        /* 使用支持窄型數據操作的右移指令vshrn_n_u16, 將綠色通道的數據移動的低8位, 此時需要清空非綠色通道的數據並且進行補償, 所以需要使用vshl_n_u8向左移動2bit, 移動時因為是窄型操作, 所以數據從16bit變為8bit, 從而提取出綠色通道數據 */
        vdst.val[1] = vshl_n_u8(vshrn_n_u16(vsrc, 5), 2);
        /* 先使用vshlq_n_u16將數據向左移動2bit, 然后使用窄型移動指令, 所以數據從16bit變為8bit, 從而提取出藍色通道數據 */
        vdst.val[2] = vmovn_u16(vshlq_n_u16(vsrc, 3));
        /* 循環 */
        vst3_u8(dst, vdst);
        dst+= 8*3;
        src += 8;
        count -= 8;
    }
}

作者:wipping的技術小棧
鏈接:https://www.jianshu.com/p/3212bdb50469
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM