日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

ARM SIMD 指令集:NEON 简介

發布時間:2023/12/8 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ARM SIMD 指令集:NEON 简介 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ARM SIMD 指令集:NEON 簡介

    • 一、NEON 簡介
      • 1.1、NEON 簡介
      • 1.2、NEON 使用方式
      • 1.3、編譯器自動向量化的編譯選項
        • 1.3.1 Arm Compiler 中使能自動向量化
        • 1.3.2 LLVM-clang 中使能自動向量化
        • 1.3.3 GCC 中使能自動向量化
      • 1.4、NEON intrisics 指令在x86平臺的仿真
    • 二、NEON 數據類型和指令類型
      • 2.1、NEON 數據類型
      • 2.2、 NEON 指令類型
    • 三、NEON 指令簡介
      • 3.1、數據讀取指令(內存數據加載到寄存器)
      • 3.2、數據存儲指令(寄存器數據回寫到內存 )
      • 3.3、數據處理指令
        • 3.3.1 獲取寄存器的值
        • 3.3.2 設置寄存器的值
        • 3.3.3 加減乘除運算
        • 3.3.4 邏輯運算
        • 3.3.5 數據類型轉換
        • 3.3.6 寄存器數據重排
    • 四、NEON 進階
    • 五、參考連接


一、NEON 簡介

1.1、NEON 簡介

  • SIMD,即 single instruction multiple data,單指令流多數據流,也就是說一次運算指令可以執行多個數據流,從而提高程序的運算速度,實質是通過 數據并行 來提高執行效率
  • ARM NEON 是 ARM 平臺下的 SIMD 指令集,利用好這些指令可以使程序獲得很大的速度提升。不過對很多人來說,直接利用匯編指令優化代碼難度較大,這時就可以利用 ARM NEON intrinsic 指令,它是底層匯編指令的封裝,不需要用戶考慮底層寄存器的分配,但同時又可以達到原始匯編指令的性能。
    • NEON 是一種 128 位的 SIMD 擴展指令集,由 ARMv7 引入,在 ARMv8 對其功能進行了擴展(支持向量化運算),支持包括加法、乘法、比較、移位、絕對值 、極大極小極值運算、保存和加載指令等運算
    • ARM 架構下的下一代 SIMD 指令集為 SVE(Scalable Vector Extension,可擴展矢量指令),支持可變矢量長度編程,SVE 指令集的矢量寄存器的長度最小支持 128 位,最大可以支持 2048 位,以 128 位為增量
  • ARM NEON 技術的核心是 NEON 單元,主要由四個模塊組成:NEON 寄存器文件、整型執行流水線、單精度浮點執行流水線和數據加載存儲和重排流水線
  • ARM 基本數據類型有三種:字節(Byte,8bit)、半字(Halfword,16bit)、字(Word,32bit)
  • 新的 Armv8a 架構有 32 個 128bit 向量寄存器,老的 ArmV7a 架構有 32 個 64bit(可當作 16 個128bit)向量寄存器,被用來存放向量數據,每個向量元素的類型必須相同,根據處理元素的大小可以劃分為 2/4/8/16 個通道

1.2、NEON 使用方式

  • ARM 平臺提供了四種使用 NEON 技術的方式,分別為 NEON 內嵌函數(intrinsics)、NEON 匯編、NEON 開源庫和編譯器自動向量化
    • NEON 內嵌函數:類似于普通函數調用,簡單易維護,編譯器負責將 NEON 指令替換成匯編語言的復雜任務,主要包括寄存器分配和代碼調度以及指令集重排,來達到獲取最高性能的目標
    • NEON 匯編:匯編語言相對晦澀難懂,移植較難、不便于維護,但其 效率最高
    • NEON 開源庫:如 Ne10、OpenMAX、ffmpeg、Eigen3 和 Math-neon 等
    • 編譯器自動向量化:目前大多數編譯器都具有自動向量化的功能,將 C/C++ 代碼自動替換為 SIMD 指令。從編譯技術上來說,自動向量化一般包含兩部分:循環向量化(Loop vectorization)和超字并行向量化(SLP,Superword-Level Parallelism vectorization,又稱 Basic block vectorization)
      • 循環向量化:將循環進行展開,增加循環中的執行代碼來減少循環次數
      • SLP 向量化:編譯器將多個標量運算綁定到一起,使其成為向量運算
  • 編寫代碼時要加上頭文件:#include <arm_neon.h>,編譯時要加上相應的 編譯選項:LOCAL_CFLAGS += -mcpu=cortex-a53 -mfloat-abi=softfp -mfpu=neon-vfpv4 -O3

1.3、編譯器自動向量化的編譯選項

  • 目前支持自動向量化的編譯器有 Arm Compiler 6、Arm C/C++ Compiler、LLVM-clang 以及 GCC,這幾種編譯器間的相互關系如下表所示:

1.3.1 Arm Compiler 中使能自動向量化

  • 下文中 Arm Compiler 6 與 Arm C/C++ Compiler 使用 armclang 統稱,armclang 使能自動向量化配置信息如下表所示:

  • armclang 實現自動向量化示例:

# AArch32 armclang --target=arm-none-eabi -mcpu=cortex-a53 -O1 -fvectorize main.c# AArch64,O2 及以上優化等級默認啟用自動向量化 -fvectorize armclang --target=aarch64-arm-none-eabi -O2 main.c

1.3.2 LLVM-clang 中使能自動向量化

  • Android NDK 從 r13 開始以 clang 為默認編譯器,使用 Android NDK 工具鏈使能自動向量化配置參數如下表所示:
  • 在 CMake 中配置自動向量化方式如下:
# method 1 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1 -fvectorize") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1 -fvectorize")# method 2 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")

1.3.3 GCC 中使能自動向量化

  • 在 gcc 中使能自動向量化配置參數如下:

  • 在不明確配置 -mcpu 的情況下,編譯器將使用默認配置(取決于編譯工具鏈時的選項設置)進行編譯,通常情況下 -mfpu 和 -mcpu 的配置存在關聯性,對應關系如下:

  • gcc 中實現自動向量化的編譯配置如下:

# AArch32 arm-none-linux-gnueabihf-gcc -mcpu=cortex-a53 -mfpu=neon -ftree-vectorize -O2 main.c# AArch64 aarch64-none-linux-gnu-gcc -mcpu=cortex-a53 -ftree-vectorize -O2 main.c

1.4、NEON intrisics 指令在x86平臺的仿真

  • 為了便于 NEON 指令從 ARM 平臺移植到 x86 平臺使用,Intel 提供了一套轉化接口 NEON2SSE,用于將 NEON 內聯函數轉化為 Intel SIMD(SSE) 內聯函數,大部分 x86 平臺 C/C++編譯器均支持 SSE,因此只需下載并包含接口頭文件 NEON_2_SSE.h,即可在x86平臺調試 NEON 指令代碼
  • x86 上模擬實現可參考:
    • NEON_2_SSE.h 是個好東西
    • https://github.com/intel/ARM_NEON_2_x86_SSE
    • https://github.com/christophe-lyon/arm-neon-tests
# 1、編程時加上頭文件 #include "NEON_2_SSE.h"# 2、編譯時加上如下編譯選項(debug) # gdb 調試時出現value optimized out 解決方法如下: # 由于 gcc 在編譯過程中默認使用 -O2 優化選項,希望進行單步跟蹤調試時,應使用 -O0 選項 set(CMAKE_C_FLAGS "-w -mfma -mavx512f -msse4 -msse4.1 -msse4.2 -O0") set(CMAKE_CXX_FLAGS "-w -mfma -mavx512f -msse4 -msse4.1 -msse4.2 -O0")

二、NEON 數據類型和指令類型

2.1、NEON 數據類型

  • NEON 向量數據類型是根據以下模式命名的:<type><size>x<number_of_lanes>_t,eg:int8x16_t 是一個16 通道 的向量,每個通道包含一個有符號 8 位整數
  • NEON 還提供了數組向量數據類型,命名模式如下:<type><size>x<number of lanes>x<length of array>_t,eg:int8x16x4_t 是一個長度為 4 的數組,每一個數據的類型為 int8x16_t
struct int8x16x4_t {int8x16_t val[4]; // 數組元素的長度范圍 2 ~ 4};

  • 下表列出了 16 個 D 寄存器上的向量數據類型及 16 個 Q 寄存器上的向量數據類型
  • D 寄存器一次能處理 8 個 u8 數據,Q 寄存器一次能處理 16 個 u8 數據
D寄存器(64-bit)Q寄存器(128-bit)
int8x8_tint8x16_t
int16x4_tint16x8_t
int32x2_tint32x4_t
int64x1_tint64x2_t
uint8x8_tuint8x16_t
uint16x4_tuint16x8_t
uint32x2_tuint32x4_t
uint64x1_tuint64x2_t
float16x4_tfloat16x8_t
float32x2_tfloat32x4_t
poly8x8_tpoly8x16_t
poly16x4_tpoly16x8_t

2.2、 NEON 指令類型

NEON指令的函數名組成格式:v<mod><opname><shape><flags>_<type> ,逐元素進行操作

  • v:vector 的縮寫,表示向量
  • mod:
    • q:表示飽和計算,int8x8_t vqadd_s8(int8x8_t a, int8x8_t b); // a 加 b 的結果做飽和計算
    • h:表示折半計算,int8x8_t vhsub_s8(int8x8_t a, int8x8_t b); // a 減 b 的結果右移一位
    • d:表示加倍計算,int32x4_t vqdmull_s16(int16x4_t a, int16x4_t b); // a 乘 b 的結果擴大一倍, 最后做飽和操作
    • r:表示舍入計算,int8x8_t vrhadd_s8(int8x8_t a, int8x8_t b); // 將 a 與 b 的和減半,同時做 rounding 操作, 每個通道可以表達為: (ai + bi + 1) >> 1
    • p:表示 pairwise 計算,int8x8_t vpadd_s8(int8x8_t a, int8x8_t b); // 將 a, b 向量的相鄰數據進行兩兩和操作
  • opname:表示具體操作,比如 add,sub 等
  • shape:
    • l:表示 long,輸出向量的元素長度是輸入長度的 2 倍,uint16x8_t vaddl_u8(uint8x8_t a, uint8x8_t b);
    • w:表示 wide,第一個輸入向量和輸出向量類型一樣,且是第二個輸入向量元素長度的 2 倍,uint16x8_t vsubw_u8(uint16x8_t a, uint8x8_t b);
    • n:表示 narrow,輸出向量的元素長度是輸入長度的 1/2 倍,uint32x2_t vmovn_u64(uint64x2_t a);
    • _high:AArch64專用,而且和 l/n 配合使用,當使用 l(Long) 時,表示輸入向量只有高 64bit 有效;當使用 n(Narrow) 時,表示輸出只有高 64bit 有效,int16x8_t vsubl_high_s8(int8x16_t a, int8x16_t b); // a 和 b 只有高 64bit 參與運算
    • _n:表示有標量參與向量計算,int8x8_t vshr_n_s8(int8x8_t a, const int n); // 向量 a 中的每個元素右移 n 位
    • _lane: 指定向量中某個通道參與向量計算,int16x4_t vmul_lane_s16(int16x4_t a, int16x4_t v, const int lane); // 取向量 v 中下標為 lane 的元素與向量 a 做乘法計算
  • flags:q 表示 quad word,四字,指定函數對 128 位 Q 寄存器進行操作,不帶 q 則對 64 位 D 寄存器進行操作
  • type:表示函數的參數類型(u8/16/32/64、s8/16/32/64、f16/32 等)
  • 正常指令:
    • 生成大小相同且類型通常與操作數向量相同的結果向量,結果大于 2n2^n2n 的除以 2n2^n2n 取余數,結果小于 0 的加上 2n2^n2n
    • eg: int8x8_t vadd_s8 (int8x8_t __a, int8x8_t __b)
  • 飽和指令:
    • 當超過數據類型指定的范圍則自動限制在該范圍內(結果大于 2n?12^n - 12n?1 的截斷到 2n?12^n - 12n?1 ,結果小于 0 的截斷到 0 ),函數中用 q 來標記(在 v 之后)
    • eg: int8x8_t vqsub_s8 (int8x8_t __a, int8x8_t __b)
  • 長指令:
    • 雙字向量操作數執行運算,生成四字向量的結果,所生成的元素一般是操作數元素寬度的兩倍,并屬于同一類型,函數中用 l 來標記,結果大于 2n2^n2n 的減去 2n2^n2n (一般不會),結果小于 0 的加上 2n2^n2n (可能出現)
    • eg:int16x8_t vaddl_s8 (int8x8_t __a, int8x8_t __b)
  • 寬指令:
    • 一個雙字向量操作數和一個四字向量操作數執行運算,生成四字向量結果。所生成的元素和第一個操作數的元素是第二個操作數元素寬度的兩倍,函數中用 w 來標記
    • eg:int16x8_t vaddw_s8 (int16x8_t __a, int8x8_t __b)
  • 窄指令:
    • 四字向量操作數執行運算,并生成雙字向量結果,所生成的元素一般是操作數元素寬度的一半,函數中用 hn 來標記
    • eg: int8x8_t vaddhn_s16 (int16x8_t __a, int16x8_t __b)

三、NEON 指令簡介

  • NEON 指令執行流程如下:
// 用 float 類型的 val 值,去初始化寄存器,值為 val float32x4_t vec = vdupq_n_f32(val);

3.1、數據讀取指令(內存數據加載到寄存器)

  • 順序讀取
// vld1 -> loads a vector from memory float32x2_t temp = vld1_f32(const float32_t * __a); // load 2 float32 64-bit float32x4_t temp = vld1q_f32(const float32_t * __a); // load 4 float32 128-bit
  • 交織讀取
// vld2 -> loads 2 vector from memory,vld3 vld4 the same as vld2 // 交叉存放: a1 a2 a3 a4 -> temp.val[0]:a1 a3 ; temp.val[1]:a2 a4 float32x2x2_t temp = vld2_f32 (const float32_t * __a); // load 4 float32 64-bit float32x4x2_t temp = vld2q_f32 (const float32_t * __a); // load 8 float32 128-bit

3.2、數據存儲指令(寄存器數據回寫到內存 )

  • 順序存儲
// vst1 -> stores a vector into memory void vst1_f32 (float32_t * ptr, float32x2_t temp); // store 2 float32 64-bit void vst1q_f32 (float32_t * ptr, float32x4_t temp); // store 4 float32 64-bit
  • 交織存儲
// vst2 -> stores 2 vector into memory,It interleaves the 2 vectors into memory. void vst2_f32 (float32_t * ptr, float32x2x2_t temp); // store 4 float32 64-bit void vst2q_f32 (float32_t * ptr, float32x4x2_t temp); // store 8 float32 64-bit

3.3、數據處理指令

3.3.1 獲取寄存器的值

// 從寄存器中訪問具體元素:extract a single lane (element) from a vector uint8_t vgetq_lane_u8(uint8x16_t vec, __constrange(0,15) int lane);

3.3.2 設置寄存器的值

// 設置寄存器具體元素值:set a single lane (element) within a vector. // 注意:返回值要用參數中的 vec 寄存器來接收 uint16x8_t vsetq_lane_u16(uint16_t value, uint16x8_t vec, __constrange(0,7) int lane); eg: vec = vsetq_lane_u16(111, vec, 5);// 設置寄存器所有元素的值(以某一個通道的值):Set all lanes to the value of one lane of a vector uint8x8_t vdup_lane_u8(uint8x8_t vec, __constrange(0,7) int lane) eg: vec = vdup_lane_u8(vec, 5); // 所有元素都設置成第五通道的值// 設置寄存器所有元素的值(以某一個固定的值) uint8x16_t vmovq_n_u8(uint8_t value); eg: uint8x16_t vec = vmovq_n_u8(5); // 所有元素都設置成 5

3.3.3 加減乘除運算

  • 加法
// 正常指令加法運算 int32x2_t vadd_s32(int32x2_t __a, int32x2_t __b);// 飽和指令加法,結果超出元素類型的最大值時,元素就取最大值;小于元素類型的最小值時,元素就取最小值 int32x2_t vqadd_s32(int32x2_t __a, int32x2_t __b);// 長指令加法運算,為了防止溢出,輸出向量長度是輸入的兩倍 int64x2_t vaddl_s32(int32x2_t __a, int32x2_t __b);// 向量半加:相加結果再除 2(向下取整),ri = (ai + bi) >> 1: int32x2_t vhadd_s32(int32x2_t __a, int32x2_t __b);// 向量舍入半加:相加結果再除 2,ri = (ai + bi + 1) >> 1: int32x2_t vrhadd_s32(int32x2_t __a, int32x2_t __b);// pairwise add,r0 = a0 + a1, ...,r3 = a6 + a7, r4 = b0 + b1, ...,r7 = b6 + b7 int8x8_t vpadd_s8(int8x8_t __a, int8x8_t __b);// long pairwise add, r0 = a0 + a1, ..., r3 = a6 + a7 int16x4_t vpaddl_s8(int8x8_t __a); // Long pairwise add and accumulate,r0 = a0 + (b0 + b1), ..., r3 = a3 + (b6 + b7) int16x4_t vpadal_s8(int16x4_t __a, int8x8_t __b);// 寬指令加法運算,第一個輸入向量的長度是第二個輸入向量長度的兩倍 int64x2_t vaddw_s32(int64x2_t __a, int32x2_t __b);// 窄指令加法,結果的類型大小是輸入類型大小的一半,待驗證??? int16x4_t vaddhn_s32(int32x4_t __a, int32x4_t __b);

  • 減法
// 正常減法運算 int32x4_t vsubq_s32(int32x4_t __a, int32x4_t __b);// 飽和指令減法,結果超出元素類型的最大值時,元素就取最大值 int32x2_t vqsub_s32 (int32x2_t __a, int32x2_t __b);// 長指令減法運算,為了防止溢出 int64x2_t vsubl_s32(int32x2_t __a, int32x2_t __b);// 向量半減:相減結果再除 2,ri = (ai - bi) >> 1 int32x2_t vhsub_s32 (int32x2_t __a, int32x2_t __b)// 寬指令減法運算,第一個元素寬度大于第二個 int64x2_t vsubw_s32(int64x2_t __a, int32x2_t __b);// 窄指令減法,結果的類型大小是輸入類型大小的一半 int16x4_t vsubhn_s32 (int32x4_t __a, int32x4_t __b);

  • 乘法
// ri = ai * bi,正常指令,逐元素相乘 int32x2_t vmul_s32 (int32x2_t __a, int32x2_t __b);// ri = ai * bi, 長指令, 為了防止溢出 int64x2_t vmull_s32 (int32x2_t __a, int32x2_t __b)// ri = ai * b,有標量參與向量運算 int32x2_t vmul_n_s32 (int32x2_t __a, int32_t __b);// ri = ai * b, 長指令, 為了防止溢出 int64x2_t vmull_n_s32 (int32x2_t __a, int32_t __b);// ri = ai * b[c] int32x2_t vmul_lane_s32 (int32x2_t __a, int32x2_t __b, const int __c)// ri = ai * b[c], 長指令,為了防止溢出 int64x2_t vmull_lane_s32 (int32x2_t __a, int32x2_t __b, const int __c);// ri = sat(ai * bi) 飽和指令,當結果溢出時,取飽和值 int32x2_t vqdmulh_s32 (int32x2_t __a, int32x2_t __b);
  • 乘加
// ri = ai + bi * ci,正常指令 int32x2_t vmla_s32 (int32x2_t __a, int32x2_t __b, int32x2_t __c)// ri = ai + bi * ci,長指令 int64x2_t vmlal_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c);// ri = ai + bi * c,正常指令,乘以標量 int32x2_t vmla_n_s32 (int32x2_t __a, int32x2_t __b, int32_t __c);// ri = ai + bi * c,長指令,乘以標量 int64x2_t vmlal_n_s32 (int64x2_t __a, int32x2_t __b, int32_t __c);// ri = ai + bi * c[d] int32x2_t vmla_lane_s32 (int32x2_t __a, int32x2_t __b, int32x2_t __c, const int __d);// ri = ai + bi * c[d] 長指令 int64x2_t vmlal_lane_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c, const int __d);// ri = ai + bi * ci 在加法之前,bi、ci相乘的結果不會被四舍五入 float32x2_t vfma_f32 (float32x2_t __a, float32x2_t __b, float32x2_t __c)// ri = sat(ai + bi * c) int64x2_t vqdmlal_n_s32 (int64x2_t __a, int32x2_t __b, int32_t __c);// ri = sat(ai + bi * c[d]) int64x2_t vqdmlal_lane_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c, const int __d);
  • 乘減
// ri = ai - bi * ci int32x2_t vmls_s32 (int32x2_t __a, int32x2_t __b, int32x2_t __c);// ri = ai - bi * ci 長指令,正常指令 int64x2_t vmlsl_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c);// ri = ai - bi * c,正常指令,乘以標量 int32x2_t vmls_n_s32 (int32x2_t __a, int32x2_t __b, int32_t __c);// ri = ai - bi * c,長指令,乘以標量 int64x2_t vmlsl_n_s32 (int64x2_t __a, int32x2_t __b, int32_t __c);// ri = ai - bi * c[d] int32x2_t vmls_lane_s32 (int32x2_t __a, int32x2_t __b, int32x2_t __c, const int __d);// ri = ai - bi * c[d] 長指令 int64x2_t vmlsl_lane_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c, const int __d); // ri = ai - bi * ci 在減法之前,bi、ci相乘的結果不會被四舍五入 float32x2_t vfms_f32 (float32x2_t __a, float32x2_t __b, float32x2_t __c);// ri = sat(ai - bi * c) int64x2_t vqdmlsl_n_s32 (int64x2_t __a, int32x2_t __b, int32_t __c);// ri = sat(ai - bi * c[d]) int64x2_t vqdmlsl_lane_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c, const int __d);
  • 倒數/平方根
// finds an approximate reciprocal of each element in a vector float32x2_t vrecpe_f32 (float32x2_t __a); // 注:vrecpe_type 計算倒數能保證千分之一左右的精度,如 1.0 的倒數為 0.998047 // 執行完如下語句后能提高百萬分之一精度 // float32x4_t recip = vrecpeq_f32(src); 此時能達到千分之一左右的精度,如 1.0 的倒數為 0.998047 // recip = vmulq_f32 (vrecpsq_f32 (src, rec), rec); 執行后能達到百萬分之一左右的精度,如1.0的倒數為0.999996 // recip = vmulq_f32 (vrecpsq_f32 (src, rec), rec); 再次執行后能基本能達到完全精度,如1.0的倒數為1.000000// performs a Newton-Raphson step for finding the reciprocal float32x2_t vrecps_f32 (float32x2_t a, float32x2_t b); float32x4_t vrecpsq_f32(float32x4_t a, float32x4_t b);// 近似平方根 float32x2_t vrsqrts_f32(float32x2_t a, float32x2_t b); float32x4_t vrsqrtsq_f32(float32x4_t a, float32x4_t b);
  • 取負
// vneg -> ri = -ai int32x2_t vneg_s32 (int32x2_t __a);

3.3.4 邏輯運算

  • 取整
/*--1、to nearest, ties to even--*/ float32x2_t vrndn_f32 (float32x2_t __a); /*--2、to nearest, ties away from zero--*/ float32x2_t vrnda_f32 (float32x2_t __a); /*--3、towards +Inf--*/ float32x2_t vrndp_f32 (float32x2_t __a);/*--4、towards -Inf--*/ float32x2_t vrndm_f32 (float32x2_t __a); /*--5、towards 0--*/ float32x2_t vrnd_f32 (float32x2_t __a);
  • 比較運算:注意返回類型為無符號整數類型
// 邏輯比較操作,結果為 true,則該元素的所有 bit 位被設置為 1;結果為 false,則該元素的所有 bit 位被設置為 0 // 注意返回類型為無符號整數類型// compares equal : vceq -> ri = ai == bi ? 1...1 : 0...0 uint32x2_t vceq_s32 (int32x2_t __a, int32x2_t __b); // compares greater-than or equal : vcge-> ri = ai >= bi ? 1...1:0...0 uint32x2_t vcge_s32 (int32x2_t __a, int32x2_t __b);// compares less-than or equal : vcle -> ri = ai <= bi ? 1...1:0...0 uint32x2_t vcle_s32 (int32x2_t __a, int32x2_t __b); // compares greater-than : vcgt -> ri = ai > bi ? 1...1:0...0 uint32x2_t vcgt_s32 (int32x2_t __a, int32x2_t __b);// compares less-than : vclt -> ri = ai < bi ? 1...1:0...0 uint32x2_t vclt_s32 (int32x2_t __a, int32x2_t __b);// 向量的絕對值比較 // compares absolute greater-than or equal : vcage -> ri = |ai| >= |bi| ? 1...1:0...0; uint32x2_t vcage_f32 (float32x2_t __a, float32x2_t __b); // compares absolute less-than or equal : vcale -> ri = |ai| <= |bi| ? 1...1:0...0; uint32x2_t vcale_f32 (float32x2_t __a, float32x2_t __b);// compares absolute greater-than : vcage -> ri = |ai| > |bi| ? 1...1:0...0; uint32x2_t vcagt_f32 (float32x2_t __a, float32x2_t __b);// compares absolute less-than : vcalt -> ri = |ai| < |bi| ? 1...1:0...0; uint32x2_t vcalt_f32 (float32x2_t __a, float32x2_t __b); // 向量與不等于零判斷 // vtst -> ri = (ai & bi != 0) ? 1...1:0...0; uint32x2_t vtst_s32 (int32x2_t __a, int32x2_t __b);
  • 絕對值
// Absolute : vabs -> ri = |ai| int32x2_t vabs_s32 (int32x2_t __a);// Absolute difference : vabd -> ri = |ai - bi| int32x2_t vabd_s32 (int32x2_t __a, int32x2_t __b);// Absolute difference and accumulate: vaba -> ri = ai + |bi - ci| int32x2_t vaba_s32 (int32x2_t __a, int32x2_t __b, int32x2_t __c);
  • 最大最小值
// vmax -> ri = ai >= bi ? ai : bi; 取向量元素中較大的那一個輸出 int32x2_t vmax_s32 (int32x2_t __a, int32x2_t __b);// vmin -> ri = ai >= bi ? bi : ai; int32x2_t vmin_s32 (int32x2_t __a, int32x2_t __b);// compares adjacent pairs of elements, 獲取相鄰對的最大值 // vpmax -> vpmax r0 = a0 >= a1 ? a0 : a1, ..., r4 = b0 >= b1 ? b0 : b1, ...; int32x2_t vpmax_s32 (int32x2_t __a, int32x2_t __b); // compares adjacent pairs of elements, 獲取相鄰對的最小值 // vpmin -> r0 = a0 >= a1 ? a1 : a0, ..., r4 = b0 >= b1 ? b1 : b0, ...; int32x2_t vpmin_s32 (int32x2_t __a, int32x2_t __b);
  • 移位運算:第二個參數是 int 型,參數均為 vector 的時候可為負數
// Vector shift left: vshl -> ri = ai << bi,如果 bi 是負數,則變成右移 // The bits shifted out of each element are lost uint16x8_t vshlq_u16 (uint16x8_t __a, int16x8_t __b); // 正常指令 uint16x8_t vrshlq_u16 (uint16x8_t __a, int16x8_t __b); // 正常指令結果 + 四舍五入 uint16x8_t vqshlq_u16 (uint16x8_t __a, int16x8_t __b); // 飽和指令截斷到 (0,65535) uint16x8_t vqrshlq_u16 (uint16x8_t __a, int16x8_t __b); // 飽和指令截斷到 (0,65535) + 四舍五入// Vector shift left by constant: vshlq -> ri = ai << b,如果 b 是負數,則變成右移 // The bits shifted out of the left of each element are lost uint16x8_t vshlq_n_u16(uint16x8_t a, __constrange(0,15) int b); // 正常指令 uint16x8_t vqshlq_n_u16(uint16x8_t a, __constrange(0,15) int b); // 飽和指令截斷到 (0,65535), ri = sat(ai << b);// Vector signed->unsigned rounding narrowing saturating shift right by constant uint8x8_t vqrshrun_n_s16 (int16x8_t __a, const int __b); // 移位后舍入// Vector shift right:可以通過左移傳入負數來實現// Vector shift left by constant: vshrq -> ri = ai >> b uint16x8_t vshrq_n_u16(uint16x8_t a, __constrange(1,16) int b); uint16x8_t vrshrq_n_u16(uint16x8_t a, __constrange(1,16) int b); // 右移累加,vsra -> ri = (ai >> c) + (bi >> c); uint16x8_t vsraq_n_u16(uint16x8_t a, uint16x8_t b, __constrange(1,16) int c); uint16x8_t vrsraq_n_u16(uint16x8_t a, uint16x8_t b, __constrange(1,16) int c);/*--Vector shift left and insert: vsli ->; The least significant bit in each element in the destination vector is unchanged. left shifts each element in the second input vector by an immediate value, and inserts the results in the destination vector. It does not affect the lowest n significant bits of the elements in the destination register. Bits shifted out of the left of each element are lost. The first input vector holds the elements of the destination vector before the operation is performed.--*/ uint16x8_t vsliq_n_u16 (uint16x8_t __a, uint16x8_t __b, const int __c);/*--Vector shift right and insert: vsri -> ; The two most significant bits in the destination vector are unchanged. right shifts each element in the second input vector by an immediate value, and inserts the results in the destination vector. It does not affect the highest n significant bits of the elements in the destination register. Bits shifted out of the right of each element are lost.The first input vector holds the elements of the destination vector before the operation is performed.--*/ uint16x8_t vsriq_n_u16 (uint16x8_t __a, uint16x8_t __b, const int __c);
  • 按位運算
// vmvn -> ri = ~ai int32x2_t vmvn_s32 (int32x2_t __a);// vand -> ri = ai & bi int32x2_t vand_s32 (int32x2_t __a, int32x2_t __b);// vorr -> ri = ai | bi int32x2_t vorr_s32 (int32x2_t __a, int32x2_t __b);// veor -> ri = ai ^ bi int32x2_t veor_s32 (int32x2_t __a, int32x2_t __b);// vbic -> ri = ~ai & bi int32x2_t vbic_s32 (int32x2_t __a, int32x2_t __b);// vorn -> ri = ai | (~bi) int32x2_t vorn_s32 (int32x2_t __a, int32x2_t __b);

3.3.5 數據類型轉換

// 浮點轉定點 // 在 f32 轉到 u32 時,是向下取整,且如果是負數,則轉換后為 0 uint32x4_t vcvtq_u32_f32(float32x4_t a); uint32x4_t vcvtq_n_u32_f32(float32x4_t a, __constrange(1,32) int b);// 定點轉浮點 float32x4_t vcvtq_f32_u32(uint32x4_t a); float32x4_t vcvtq_n_f32_u32(uint32x4_t a, __constrange(1,32) int b);// 浮點之間轉換 float16x4_t vcvt_f16_f32(float32x4_t a); // VCVT.F16.F32 d0, q0 float32x4_t vcvt_f32_f16(float16x4_t a); // 定點之間轉換 int16x8_t vmovl_s8 (int8x8_t a); int8x8_t vqmovn_s16 (int16x8_t a); int32x4_t vmovl_s16 (int16x4_t a); int16x4_t vqmovn_s32 (int32x4_t a);// 向量重新解釋類型轉換運算:將元素類型為 type2 的 vector 轉換為元素類型為 type1 的 vector // 將向量視為另一類型而不更改其值 float32x2_t vreinterpret_f32_u32 (uint32x2_t __a);

3.3.6 寄存器數據重排

  • 按索引重排
// vext -> 提取第二個 vector 的低端的 c 個元素和第一個 vector 的高端的剩下的幾個元素 // 如:src1 = {1,2,3,4,5,6,7,8}// src2 = {9,10,11,12,13,14,15,16}// dst = vext_s8(src1,src2,3)時,則dst = {4,5,6,7,8, 9,10,11} int8x8_t vext_s8 (int8x8_t __a, int8x8_t __b, const int __c);// vtbl1 -> 第二個vector是索引,根據索引去第一個vector(相當于數組)中搜索相應的元素 // 并輸出新的vector,超過范圍的索引返回的是 0 // 如:src1 = {1,2,3,4,5,6,7,8} // src2 = {0,0,1,1,2,2,7,8} // dst = vtbl1_u8(src1,src2)時,則dst = {1,1,2,2,3,3,8,0} int8x8_t vtbl1_s8 (int8x8_t __a, int8x8_t __b); // vtbl2 -> 數組長度擴大到2個vector // 如:src.val[0] = {1,2,3,4,5,6,7,8}// src.val[1] = {9,10,11,12,13,14,15,16}// src2 = {0,0,1,1,2,2,8,10}// dst = vtbl2_u8(src,src2)時,則 dst = {1,1,2,2,3,3,9,11} int8x8_t vtbl2_s8 (int8x8x2_t __a, int8x8_t __b); //vtbl3 vtbl4類似// vtbx1 -> 與vtbl1功能一樣,不過搜索到的元素是用來替換第一個vector中的元素, // 并輸出替換后的新vector,當索引超出范圍時,則不替換第一個vector中相應的元素。 int8x8_t vtbx1_s8 (int8x8_t __a, int8x8_t __b, int8x8_t __c); // vtbx2 vtbx3 vtbx4類似// vbsl -> Bitwise Select, 按位選擇,參數為(mask, src1, src2) // mask 的某個 bit 為1,則選擇 src1 中對應的 bit,為 0,則選擇 src2 中對應的 bit int8x8_t vbsl_s8 (uint8x8_t __a, int8x8_t __b, int8x8_t __c);
  • 反轉向量元素
// vrev -> 將vector中的元素位置反轉 // 如:src1 = {1,2,3,4,5,6,7,8} // dst = vrev64_s8(src1)時,則dst = {8,7,6,5,4,3,2,1} int8x8_t vrev64_s8 (int8x8_t __a); // 如:src1 = {1,2,3,4,5,6,7,8} // dst = vrev32_s8(src1)時,則dst = {4,3,2,1,8,7,6,5} int8x8_t vrev32_s8 (int8x8_t __a); // 如:src1 = {1,2,3,4,5,6,7,8} // dst = vrev16_s8(src1)時,則dst = {2,1,4,3,6,5,8,7} int8x8_t vrev16_s8 (int8x8_t __a);
  • 轉置
// vtrn -> 將兩個輸入 vector 的元素通過轉置生成一個有兩個 vector 的矩陣 // 如:src.val[0] = {1,2,3,4,5,6,7,8} // src.val[1] = {9,10,11,12,13,14,15,16} // dst = vtrn_u8(src.val[0], src.val[1])時, // 則 dst.val[0] = {1,9, 3,11,5,13,7,15} // dst.val[1] = {2,10,4,12,6,14,8,16} int8x8x2_t vtrn_s8 (int8x8_t __a, int8x8_t __b);
  • 交叉
// vzip_type: 將兩個輸入 vector 的元素通過交叉生成一個有兩個vector的矩陣 // 如:src.val[0] = {1,2,3,4,5,6,7,8} // src.val[1] = {9,10,11,12,13,14,15,16} // dst = vzip_u8(src.val[0], src.val[1])時, // 則dst.val[0] = {1,9, 2,10,3,11,4,12} // dst.val[1] = {5,13,6,14,7,15,8,16} int8x8x2_t vzip_s8 (int8x8_t __a, int8x8_t __b);
  • 反交叉
// vuzp_type: 將兩個輸入vector的元素通過反交叉生成一個有兩個vector的矩陣(通過這個可實現n-way 交織) // 如:src.val[0] = {1,2,3,4,5,6,7,8} // src.val[1] = {9,10,11,12,13,14,15,16} // dst = vuzp_u8(src.val[0], src.val[1])時, // 則dst.val[0] = {1,3,5,7,9, 11,13,15} // dst.val[1] = {2,4,6,8,10,12,14,16} int8x8x2_t vuzp_s8 (int8x8_t __a, int8x8_t __b);
  • 組合向量:將兩個 64 位向量組合為單個 128 位向量
// vcombine -> 將兩個元素類型相同的輸入 vector 拼接成一個同類型但大小是輸入vector兩倍的新vector。 uint8x16_t vcombine_u8(uint8x8_t low, uint8x8_t high);
  • 拆分向量:將一個 128 位向量拆分為 2 個 64 位向量
// 從寄存器中獲取低半部分元素 uint8x8_t vget_low_u8(uint8x16_t a);// 從寄存器中獲取高半部分元素 uint8x8_t vget_high_u8(uint8x16_t a);

四、NEON 進階

  • CPU優化技術 - NEON 開發進階:對齊問題解決

  • ARM 官方算子優化:https://github.com/ARM-software/ComputeLibrary

  • NCNN NEON 優化參考:包含常用算子 sigmoid/softmax/relu 等

  • OPENCV 第三方庫 carotene NEON 算子優化

  • NEON 使用建議:

    • 每次讀入的數據盡可能的占滿 128 位
    • 除法使用乘法進行代替,浮點計算使用定點加移位的方式進行
    • 合并算法種的一些系數,進行化簡
    • 算子進行融合,避免內存的多次讀寫
    • 使用多核多線程進行加速

五、參考連接

1、Neon Intrinsics各函數介紹(*****)
2、https://developer.arm.com/documentation(*****)
3、ARM Neon Intrinsics 學習指北:從入門、進階到學個通透(*****)
4、ARM NEON 技術之 NEON 基礎介紹(***)
5、移動端算法優化(******)
6、利用 ARM NEON intrinsic 優化常用數學運算(***)

總結

以上是生活随笔為你收集整理的ARM SIMD 指令集:NEON 简介的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。