ARM平台上的NEON加速基础
[TOC]
概述
NEON加速指令介绍
汇编指令格式
AArch64 与AArch32 / Armv7-A 的 NEON 汇编指令除了种类上存在差异,格式上也存在很大差异。
其中指令中有一些通用的书写格式, 含义如下:
- {}, 表示可选项
- <>, 表示必选项
AArch64汇编指令格式
1 | {<prefix>}<op>{<suffix>} Vd.<T>, Vn.<T>, Vm.<T> |
<prefix> 表示前缀名字,包括以下几类:
- S/U/F/P:表示数据类型,分别为 有符号整型/无符号整型/浮点型/布尔型。
- Q:表示饱和(Saturating)计算。
- R:表示舍入(Rounding)计算, Rounding 操作等价于加上 0.5 之后再截断。
- H:表示折半(Halving)计算。
- D:表示翻倍(Doubling)算。
<op> 表示具体的操作,例如 ADD
,SUB
等等
<suffix> 表示后缀名字,包括以下几类:
V:表示 Reduction 计算。
P:表示 Pairwise 计算。
H:表示结果只取每个通道的高半部分(High)。
L/N/W/L2/N2/W2:表示数据长度的变化
- L / L2 :表示输出向量是输入向量长度的 2 倍,其中 L 表示输入寄存器的低 64bit 数据有效,L2 表示输入寄存器的高 64bit 数据有效。
L/L2
- N/N2:表示输出向量是输入向量的 1/2 倍,N 表示输出向量只有低 64bit 有效,N2 则表示输出向量只有高 64bit 有效。
N/N2
- W/W2:表示输出向量和第一个输入向量长度相等,且这两个向量是第二个向量长度的 2 倍,其中 W 表示第二个输入向量的低 64bit 有效,W2 表示第二输入向量的高 64bit 有效。
W/W2
4)<T> 表示单个通道的数据类型,8B/16B/4H/8H/2S/4S/2D,B 表示 8bit,H 表示 16bit,S 表示 32bit,D 表示 64bit。
汇编指令示例
SQRSHRN2
表示对向量进行 Rouding 类型的右移操作,并对结果做饱和计算,最后将结果赋给目的向量的高半部分,并保持低半部分不改变,具体示例如下
1 | // 指令语句作用: |
AArch32 / Armv7汇编指令格式
1 | V{<mod>}<op>{<shape>}{<cond>}{.<dt>}<dest1>{,<dest2>},<src1>{,<src2>} |
1)V AArch32 / Armv7 的汇编指令以”V”开头
2)<mod> 该修饰字可以表示为以下类型:
- Q, 表示饱和(Saturating)计算。
- R, 表示舍入(Rounding)计算,Rounding 操作等价于加上 0.5 之后再截断。
- H, 表示折半(Halving)计算。
- D, 表示翻倍(Doubling)计算。
3)<op> 表示具体的操作,例如 ADD
,SUB
等等
4)<shape> 表示数据长度的变化,L/N/W。
5)<cond> 表示指令执行的条件
6).<dt> 表示数据类型,默认为第二个操作数的数据类型。如果第二个操作数不存在,为第一个操作数类型,仍不存在为结果操作数类型。
7)<dest> 表示输出操作数
**8)<src1> <src2>**表示两个输入操作数
汇编指令示例
VQDMULL
表示两向量相乘,结果乘以 2。
1 | // 指令语句作用: |
intrinsics指令格式
相比于汇编指令,NEON Intrinsics 是一种更简单的编写 NEON 代码的方法,NEON Intrinsics 类似于 C 函数调用,在编译时由编译器替换为相应的汇编指令,使用时需要包含头文件arm_neon.h
。
向量类型格式
1 | // 非数组向量格式 |
1)<type> 数据类型,如 int
/uint
/float
/poly
。
2)<size> 元素数据大小,如8位、16位、32位、64位。
3)
4)
NEON向量数据类型
以128位寄存器为例,数据类型如:int8x16_t,int16x8_t,int32x4_t,int64x2_t,uint8x16_t,uint168_t,uint32x4_t,uint64x2_t,float16x8_t,float32x4_t
数据类型 | 64-bit type(D-register) | 128-bit type(Q-register) | 类型说明 | 备注 |
---|---|---|---|---|
int8x8_t | int8x16_t | 8位int类型数据,8个通道/8位int类型数据,16个通道 | ||
int16x4_t | int16x8_t | 16位int类型数据,4个通道/16位int类型数据,8个通道 | ||
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 |
从上表可以看成,NEON 指令集的向量是由基本上就是常见的数据类型组成,根据 D 寄存器和 Q 寄存器以及数据类型形成多种向量类型。这些向量类型将作为 NEON 指令集的输入输出参数参与计算。
NEON 函数格式
v<mod><opname><shape><flags>_<type>
1 | v<mod><opname><shape><flags>_<type> |
1)<mod>
q:表示饱和计算,即当计算结果溢出时,结果取类型范围内的最大值或最小值,例如
1 | // a加b的结果做饱和计算 |
h:表示折半计算,例如
1 | // a减b的结果右移一位 |
d:表示加倍计算,例如
1 | // a乘b的结果扩大一倍, 最后做饱和操作 |
r:表示舍入计算,例如
1 | // 将a与b的和减半,同时做rounding 操作, 每个通道可以表达为: (ai + bi + 1) >> 1 |
p:表示pairwise计算。例如
1 | // 将a, b向量的相邻数据进行两两和操作 |
2) <opname>
表示具体操作,如加法:add;减法:sub;乘法:mul;加载数据:ld;读取数据:st
3) <shape>
计算形式
l:表示long,输出向量的元素长度是输入长度的2倍,例如
1 | uint16x8_t vaddl_u8(uint8x8_t a, uint8x8_t b); |
n:表示 narrow,输出向量的元素长度是输入长度的1/2倍,例如
1 | uint32x2_t vmovn_u64(uint64x2_t a); |
w:表示 wide,第一个输入向量和输出向量类型一样,且是第二个输入向量元素长度的2倍,例如
1 | uint16x8_t vsubw_u8(uint16x8_t a, uint8x8_t b); |
_high:AArch64专用,而且和 l/n 配合使用。
- 当使用 l(Long) 时,表示输入向量只有高 64bit 有效;
- 当使用 n(Narrow) 时,表示输出只有高 64bit 有效。
1 | // a 和 b 只有高 64bit 参与运算 |
_n:表示有标量参与向量计算,例如
1 | // 向量 a 中的每个元素右移 n 位 |
_lane: 指定向量中某个通道参与向量计算,例如
1 | // 取向量 v 中下标为 lane 的元素与向量 a 做乘法计算 |
4) <flags>
q:寄存器长度,若存在q时,表示使用128位的寄存器,否则使用64位寄存器。
5) <type>
表示单个通道的数据类型
有u8
、s8
、u16
、s16
、u32
、s32
、f32
、f64
。(s表示int)
解释说明
1 | vadd_u16:// 两个uint16x4相加为一个uint16x4 |