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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

ARM SIMD 指令集:NEON 简介

發(fā)布時(shí)間:2023/12/8 编程问答 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ARM SIMD 指令集:NEON 简介 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

ARM SIMD 指令集:NEON 簡(jiǎn)介

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


一、NEON 簡(jiǎn)介

1.1、NEON 簡(jiǎn)介

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

1.2、NEON 使用方式

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

1.3、編譯器自動(dòng)向量化的編譯選項(xiàng)

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

1.3.1 Arm Compiler 中使能自動(dòng)向量化

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

  • armclang 實(shí)現(xiàn)自動(dòng)向量化示例:

# AArch32 armclang --target=arm-none-eabi -mcpu=cortex-a53 -O1 -fvectorize main.c# AArch64,O2 及以上優(yōu)化等級(jí)默認(rèn)啟用自動(dòng)向量化 -fvectorize armclang --target=aarch64-arm-none-eabi -O2 main.c

1.3.2 LLVM-clang 中使能自動(dòng)向量化

  • Android NDK 從 r13 開始以 clang 為默認(rèn)編譯器,使用 Android NDK 工具鏈?zhǔn)鼓茏詣?dòng)向量化配置參數(shù)如下表所示:
  • 在 CMake 中配置自動(dòng)向量化方式如下:
# 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 中使能自動(dòng)向量化

  • 在 gcc 中使能自動(dòng)向量化配置參數(shù)如下:

  • 在不明確配置 -mcpu 的情況下,編譯器將使用默認(rèn)配置(取決于編譯工具鏈時(shí)的選項(xiàng)設(shè)置)進(jìn)行編譯,通常情況下 -mfpu 和 -mcpu 的配置存在關(guān)聯(lián)性,對(duì)應(yīng)關(guān)系如下:

  • gcc 中實(shí)現(xiàn)自動(dòng)向量化的編譯配置如下:

# 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平臺(tái)的仿真

  • 為了便于 NEON 指令從 ARM 平臺(tái)移植到 x86 平臺(tái)使用,Intel 提供了一套轉(zhuǎn)化接口 NEON2SSE,用于將 NEON 內(nèi)聯(lián)函數(shù)轉(zhuǎn)化為 Intel SIMD(SSE) 內(nèi)聯(lián)函數(shù),大部分 x86 平臺(tái) C/C++編譯器均支持 SSE,因此只需下載并包含接口頭文件 NEON_2_SSE.h,即可在x86平臺(tái)調(diào)試 NEON 指令代碼
  • x86 上模擬實(shí)現(xiàn)可參考:
    • NEON_2_SSE.h 是個(gè)好東西
    • https://github.com/intel/ARM_NEON_2_x86_SSE
    • https://github.com/christophe-lyon/arm-neon-tests
# 1、編程時(shí)加上頭文件 #include "NEON_2_SSE.h"# 2、編譯時(shí)加上如下編譯選項(xiàng)(debug) # gdb 調(diào)試時(shí)出現(xiàn)value optimized out 解決方法如下: # 由于 gcc 在編譯過(guò)程中默認(rèn)使用 -O2 優(yōu)化選項(xiàng),希望進(jìn)行單步跟蹤調(diào)試時(shí),應(yīng)使用 -O0 選項(xiàng) 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 數(shù)據(jù)類型和指令類型

2.1、NEON 數(shù)據(jù)類型

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

  • 下表列出了 16 個(gè) D 寄存器上的向量數(shù)據(jù)類型及 16 個(gè) Q 寄存器上的向量數(shù)據(jù)類型
  • D 寄存器一次能處理 8 個(gè) u8 數(shù)據(jù),Q 寄存器一次能處理 16 個(gè) u8 數(shù)據(jù)
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指令的函數(shù)名組成格式:v<mod><opname><shape><flags>_<type> ,逐元素進(jìn)行操作

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

三、NEON 指令簡(jiǎn)介

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

3.1、數(shù)據(jù)讀取指令(內(nèi)存數(shù)據(jù)加載到寄存器)

  • 順序讀取
// 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、數(shù)據(jù)存儲(chǔ)指令(寄存器數(shù)據(jù)回寫到內(nèi)存 )

  • 順序存儲(chǔ)
// 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
  • 交織存儲(chǔ)
// 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、數(shù)據(jù)處理指令

3.3.1 獲取寄存器的值

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

3.3.2 設(shè)置寄存器的值

// 設(shè)置寄存器具體元素值:set a single lane (element) within a vector. // 注意:返回值要用參數(shù)中的 vec 寄存器來(lái)接收 uint16x8_t vsetq_lane_u16(uint16_t value, uint16x8_t vec, __constrange(0,7) int lane); eg: vec = vsetq_lane_u16(111, vec, 5);// 設(shè)置寄存器所有元素的值(以某一個(gè)通道的值):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); // 所有元素都設(shè)置成第五通道的值// 設(shè)置寄存器所有元素的值(以某一個(gè)固定的值) uint8x16_t vmovq_n_u8(uint8_t value); eg: uint8x16_t vec = vmovq_n_u8(5); // 所有元素都設(shè)置成 5

3.3.3 加減乘除運(yùn)算

  • 加法
// 正常指令加法運(yùn)算 int32x2_t vadd_s32(int32x2_t __a, int32x2_t __b);// 飽和指令加法,結(jié)果超出元素類型的最大值時(shí),元素就取最大值;小于元素類型的最小值時(shí),元素就取最小值 int32x2_t vqadd_s32(int32x2_t __a, int32x2_t __b);// 長(zhǎng)指令加法運(yùn)算,為了防止溢出,輸出向量長(zhǎng)度是輸入的兩倍 int64x2_t vaddl_s32(int32x2_t __a, int32x2_t __b);// 向量半加:相加結(jié)果再除 2(向下取整),ri = (ai + bi) >> 1: int32x2_t vhadd_s32(int32x2_t __a, int32x2_t __b);// 向量舍入半加:相加結(jié)果再除 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);// 寬指令加法運(yùn)算,第一個(gè)輸入向量的長(zhǎng)度是第二個(gè)輸入向量長(zhǎng)度的兩倍 int64x2_t vaddw_s32(int64x2_t __a, int32x2_t __b);// 窄指令加法,結(jié)果的類型大小是輸入類型大小的一半,待驗(yàn)證??? int16x4_t vaddhn_s32(int32x4_t __a, int32x4_t __b);

  • 減法
// 正常減法運(yùn)算 int32x4_t vsubq_s32(int32x4_t __a, int32x4_t __b);// 飽和指令減法,結(jié)果超出元素類型的最大值時(shí),元素就取最大值 int32x2_t vqsub_s32 (int32x2_t __a, int32x2_t __b);// 長(zhǎng)指令減法運(yùn)算,為了防止溢出 int64x2_t vsubl_s32(int32x2_t __a, int32x2_t __b);// 向量半減:相減結(jié)果再除 2,ri = (ai - bi) >> 1 int32x2_t vhsub_s32 (int32x2_t __a, int32x2_t __b)// 寬指令減法運(yùn)算,第一個(gè)元素寬度大于第二個(gè) int64x2_t vsubw_s32(int64x2_t __a, int32x2_t __b);// 窄指令減法,結(jié)果的類型大小是輸入類型大小的一半 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, 長(zhǎng)指令, 為了防止溢出 int64x2_t vmull_s32 (int32x2_t __a, int32x2_t __b)// ri = ai * b,有標(biāo)量參與向量運(yùn)算 int32x2_t vmul_n_s32 (int32x2_t __a, int32_t __b);// ri = ai * b, 長(zhǎng)指令, 為了防止溢出 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], 長(zhǎng)指令,為了防止溢出 int64x2_t vmull_lane_s32 (int32x2_t __a, int32x2_t __b, const int __c);// ri = sat(ai * bi) 飽和指令,當(dāng)結(jié)果溢出時(shí),取飽和值 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,長(zhǎng)指令 int64x2_t vmlal_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c);// ri = ai + bi * c,正常指令,乘以標(biāo)量 int32x2_t vmla_n_s32 (int32x2_t __a, int32x2_t __b, int32_t __c);// ri = ai + bi * c,長(zhǎng)指令,乘以標(biāo)量 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] 長(zhǎng)指令 int64x2_t vmlal_lane_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c, const int __d);// ri = ai + bi * ci 在加法之前,bi、ci相乘的結(jié)果不會(huì)被四舍五入 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 長(zhǎng)指令,正常指令 int64x2_t vmlsl_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c);// ri = ai - bi * c,正常指令,乘以標(biāo)量 int32x2_t vmls_n_s32 (int32x2_t __a, int32x2_t __b, int32_t __c);// ri = ai - bi * c,長(zhǎng)指令,乘以標(biāo)量 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] 長(zhǎng)指令 int64x2_t vmlsl_lane_s32 (int64x2_t __a, int32x2_t __b, int32x2_t __c, const int __d); // ri = ai - bi * ci 在減法之前,bi、ci相乘的結(jié)果不會(huì)被四舍五入 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);
  • 倒數(shù)/平方根
// finds an approximate reciprocal of each element in a vector float32x2_t vrecpe_f32 (float32x2_t __a); // 注:vrecpe_type 計(jì)算倒數(shù)能保證千分之一左右的精度,如 1.0 的倒數(shù)為 0.998047 // 執(zhí)行完如下語(yǔ)句后能提高百萬(wàn)分之一精度 // float32x4_t recip = vrecpeq_f32(src); 此時(shí)能達(dá)到千分之一左右的精度,如 1.0 的倒數(shù)為 0.998047 // recip = vmulq_f32 (vrecpsq_f32 (src, rec), rec); 執(zhí)行后能達(dá)到百萬(wàn)分之一左右的精度,如1.0的倒數(shù)為0.999996 // recip = vmulq_f32 (vrecpsq_f32 (src, rec), rec); 再次執(zhí)行后能基本能達(dá)到完全精度,如1.0的倒數(shù)為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);
  • 取負(fù)
// vneg -> ri = -ai int32x2_t vneg_s32 (int32x2_t __a);

3.3.4 邏輯運(yùn)算

  • 取整
/*--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);
  • 比較運(yùn)算:注意返回類型為無(wú)符號(hào)整數(shù)類型
// 邏輯比較操作,結(jié)果為 true,則該元素的所有 bit 位被設(shè)置為 1;結(jié)果為 false,則該元素的所有 bit 位被設(shè)置為 0 // 注意返回類型為無(wú)符號(hào)整數(shù)類型// 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);// 向量的絕對(duì)值比較 // 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);
  • 絕對(duì)值
// 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; 取向量元素中較大的那一個(gè)輸出 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, 獲取相鄰對(duì)的最大值 // 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, 獲取相鄰對(duì)的最小值 // vpmin -> r0 = a0 >= a1 ? a1 : a0, ..., r4 = b0 >= b1 ? b1 : b0, ...; int32x2_t vpmin_s32 (int32x2_t __a, int32x2_t __b);
  • 移位運(yùn)算:第二個(gè)參數(shù)是 int 型,參數(shù)均為 vector 的時(shí)候可為負(fù)數(shù)
// Vector shift left: vshl -> ri = ai << bi,如果 bi 是負(fù)數(shù),則變成右移 // 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); // 正常指令結(jié)果 + 四舍五入 uint16x8_t vqshlq_u16 (uint16x8_t __a, int16x8_t __b); // 飽和指令截?cái)嗟?(0,65535) uint16x8_t vqrshlq_u16 (uint16x8_t __a, int16x8_t __b); // 飽和指令截?cái)嗟?(0,65535) + 四舍五入// Vector shift left by constant: vshlq -> ri = ai << b,如果 b 是負(fù)數(shù),則變成右移 // 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); // 飽和指令截?cái)嗟?(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:可以通過(guò)左移傳入負(fù)數(shù)來(lái)實(shí)現(xiàn)// 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);
  • 按位運(yùn)算
// 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 數(shù)據(jù)類型轉(zhuǎn)換

// 浮點(diǎn)轉(zhuǎn)定點(diǎn) // 在 f32 轉(zhuǎn)到 u32 時(shí),是向下取整,且如果是負(fù)數(shù),則轉(zhuǎn)換后為 0 uint32x4_t vcvtq_u32_f32(float32x4_t a); uint32x4_t vcvtq_n_u32_f32(float32x4_t a, __constrange(1,32) int b);// 定點(diǎn)轉(zhuǎn)浮點(diǎn) float32x4_t vcvtq_f32_u32(uint32x4_t a); float32x4_t vcvtq_n_f32_u32(uint32x4_t a, __constrange(1,32) int b);// 浮點(diǎn)之間轉(zhuǎn)換 float16x4_t vcvt_f16_f32(float32x4_t a); // VCVT.F16.F32 d0, q0 float32x4_t vcvt_f32_f16(float16x4_t a); // 定點(diǎn)之間轉(zhuǎn)換 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);// 向量重新解釋類型轉(zhuǎn)換運(yùn)算:將元素類型為 type2 的 vector 轉(zhuǎn)換為元素類型為 type1 的 vector // 將向量視為另一類型而不更改其值 float32x2_t vreinterpret_f32_u32 (uint32x2_t __a);

3.3.6 寄存器數(shù)據(jù)重排

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

四、NEON 進(jìn)階

  • CPU優(yōu)化技術(shù) - NEON 開發(fā)進(jìn)階:對(duì)齊問(wèn)題解決

  • ARM 官方算子優(yōu)化:https://github.com/ARM-software/ComputeLibrary

  • NCNN NEON 優(yōu)化參考:包含常用算子 sigmoid/softmax/relu 等

  • OPENCV 第三方庫(kù) carotene NEON 算子優(yōu)化

  • NEON 使用建議:

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

五、參考連接

1、Neon Intrinsics各函數(shù)介紹(*****)
2、https://developer.arm.com/documentation(*****)
3、ARM Neon Intrinsics 學(xué)習(xí)指北:從入門、進(jìn)階到學(xué)個(gè)通透(*****)
4、ARM NEON 技術(shù)之 NEON 基礎(chǔ)介紹(***)
5、移動(dòng)端算法優(yōu)化(******)
6、利用 ARM NEON intrinsic 優(yōu)化常用數(shù)學(xué)運(yùn)算(***)

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。