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 个通道


一般嵌入式设备上 arm_neon.h 头文件在各自的交叉编译器下的 include 目录下
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
2
3
4
5
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
2
3
4
5
6
7
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
2
3
4
5
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")
1
2
3
4
5
6
7
8
二、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
 };

1
2
3
4
5


下表列出了 16 个 D 寄存器上的向量数据类型及 16 个 Q 寄存器上的向量数据类型
D 寄存器一次能处理 8 个 u8 数据,Q 寄存器一次能处理 16 个 u8 数据
D寄存器(64-bit)    Q寄存器(128-bit)
int8x8_t    int8x16_t
int16x4_t    int16x8_t
int32x2_t    int32x4_t
int64x1_t    int64x2_t
uint8x8_t    uint8x16_t
uint16x4_t    uint16x8_t
uint32x2_t    uint32x4_t
uint64x1_t    uint64x2_t
float16x4_t    float16x8_t
float32x2_t    float32x4_t
poly8x8_t    poly8x16_t
poly16x4_t    poly16x8_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:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值