SIMD是使用一条指令同时处理多个数据流的计算技术,可以理解为将原先的一次计算一个值改为一次并行计算多个值。这种并行技术是通过将多个数据打包传输至向量寄存器,用CPU核心一次计算处理来实现的。
自动向量化
编译器自动向量化
Gcc编译器自动向量化的选项有-ftree-loop-vectorize、-ftree-slp-vectorize、-ftree-vectorize。用户如果使用的优化级别为-O2、-O3,则上述编译选项已经使用。
-fdump-tree-vect选项可以打印编译过程的中间文件来分析编译器向量化的结果,找出被向量化和未向量化的基本块。同样的选项还有-fdump-tree-slp、-fopt-info-vec-optimized、 -fopt-info-vec-missed、 -fopt-info-vec-all。
编译指导语句向量化
首先包含头文件#include<omp.h>,然后使用openMP中的simd指导语句
#pragma omp simd [clause]
通过子句传递参数来定义一些自动向量化的限制条件
• 子句
• collapse(n) 将多层(紧嵌套)循环的迭代空间合并,扁平化成单层循环,再对
其进行向量化,参数n指明合并的循环的层数
• private(list) 将list中的变量私有化
• reduction(reduction-identifier:list) 规约操作 +, -, *, &, |, ^, &&, ||
• safelen(length) 指定向量依赖项的最大距离
• simdlen(length) 指定向量通道数
• linear(list[:linear-step]) 从循环变量到数组的线性映射
neon指令集
向量类型规范
<type><size>x<lanes>_t
<type>:数据类型,如int/float/poly
<size>:数据大小
<lanes>:通道数,即元素个数
向量类型数组,如:int32x4_t vec[4],代表4个向量寄存器,访问第i个向量,vec[i]
函数命名规范
v<mod><operate><shape><flags>_<type>
<mod> 只针对整型元素
• q:表示饱和计算。即算术操作发生溢出时,产生的结果是类型范围内的最大值或者最小值。
• h:表示half计算
• d:表示doule计算,通常还需要结合l,扩大向量长度
• r:表明舍入计算(实现四舍五入)
• p:表明pairwise计算
<operate>表示对向量元素的具体操作
• 加载:将数据从内存加载到寄存器,如ld
• 存储:将数据从寄存器转移到内存,如st
• 加/减法:c=a+b,如add,sub
• 乘/乘加法:c=a*b+d,如mul,mla,mls
• 逻辑判断:>,<, =>,=< ,==,如eq ,gt
• 位操作:与或非,如and,or
• 位移:按位左移,右移,如shl,shr
• 类型转换:浮点型和整型之间转换数据类型,cvt
• 标量填充:初始化向量,如dup,mov
<shape>
• l:表示long,对双字向量(64位)操作数执行运算,生成四字(128位)向量的结果。所生成的元素一般是操作数元素宽度的两倍,并属于同一类型。
• n:narrow,四字向量操作数执行运算,并生成双字向量结果,所生成的元素一般是操作数元素宽度的一半
• w:wide,一个四字向量操作数和一个双字向量操作数执行运算,生成四字向量结果。所生成的元素和第一个操作数的元素是第二个操作数元素宽度的两倍
• _high:128位向量寄存器专用,需要同 l/n 配合使用。当使用 l(Long) 时,表示输入向量只取用高half位;当使用 n(Narrow) 时,表示只赋值输出向量高half位。
• _n:表示有标量参与向量计算
• _lane: 指定向量中某个通道参与向量计算
<flags> 指明使用
• q:若存在该字符,则表示使用 128位向量寄存器;若不存在该字符,则表示使用 64bit 向量寄存器的寄存器长度
<type> 单个通道的数据类型
• u:uint,无符号整型, u8、u16、 u32
• s:int,有符号整型, s8 、s16、 s32
• f:float,浮点型, f32、f64
存储操作
初始化寄存器
• <Result_t> vcreate_<type>(<Scalar_t> N);
根据输入的64位,一次性初始化向量所有通道
如int8x8_t vcreate_s8(uint64_t)
vcreate_u8(0x0102030405060708);
• “=”直接赋值
float32x4_t vec={1.0, 2.0, 3.0, 4.0}
同常规数据类型相似,在声明向量变量的同时,通过“=”进行初始化。
• dup操作
uint16x4_t vdup_n_u16(uint16_t value)
vdup_n_type: 用类型为type的数值,初始化同数据类型输出向量的所有通道元素。
• mov操作
float32x2_t vmov_n_f32(float32_t value)
用类型为type的数值,初始化同数据类型输出向量的所有通道元素。
vmovn_type: 用旧vector创建一个新vector,新vector的元素bit位是旧vector的一半。新vector元素只保留旧vector元素的低半部分。
vmovl_type: 将vector的元素bit位扩大到原来的两倍,元素值不变。
数据存取
• int32x2x2_t vld2_s32(int32_t const * ptr)
以交叉存取方式将数据从内存ptr加载到两个向量寄存器中
• 数组向量格式,为交叉存取而定义
<type><size>x<number_of_lanes>x<length_of_array>_t
a、数组中向量个数最大为4
b、本质为结构体
typedef struct float8x8x2_t
{
float8x8_t val[2];
} float8x8x2_t;
c、访问第i个向量寄存器:vec2.val[i]
• vst2_type: 交叉存放,vld2_type的逆过程
• int16x4_t vld1_lane_s16(int16_t const * ptr, int16x4_t src, const int ilane)
vld1_lane_type:用输入向量src创建同类型的输出向量,同时将输出向量中指定ilane通道的元素值改为ptr内存地址上的首位值。
• uint16x4x2_t vld2_dup_u16(uint16_t const * ptr)
vld2_dup_type: 用type类型的ptr内存地址处第一个值,初始化输出向量数组第一位向量的所有元素;用ptr内存地后处第二个值,初始化输出向量数组第一位向量的所有元素。
算术操作
加法
• vadd_type: r[i] = a[i] + b[i]
• vqadd_type: r[i] = sat(a[i] + b[i]) 饱和指令,相加结果超出元素的最大值时,元素就取最大值。
• vaddhn_type: 结果vector元素的位数减半,是输入vector位数的一半。
• vaddl_type: 加法运算结果位数加倍,目的防止溢出
• vaddw_type: 两个输入vector元素位数不一致,第一个vector宽度需大于第二个vector
• vhadd_type: 相加结果再除2。r[i] = (a[i] + b[i] ) >> 1
• vrhadd_type: 相加结果再除2(实现四舍五入)。r[i] = (a[i] + b[i] + 1) >> 1
• vpadd_type: 向量a与向量b进行pairwise运算。
规则如:r[0] = a[0] + a[1], ..., r[3] = a[6] + a[7], r[4] = b[0] + b[1], ..., r[7] = b[6] + b[7]
• vpaddl_type: 将单个输入vector内的数据进行pairwise运算,同时结果vector的位数宽度加倍。如r[0] = a[0] + a[1], ..., r[3] = a[6] + a[7]
• vpadal_type: r[0] = a[0] + (b[0] + b[1]), ..., r[3] = a[3] + (b[6] + b[7]);
如int32x2_t vpadal_s16(int32x2_t a, int16x4_t b)
减法
• vsub_type: r[i] = a[i] – b[i]。向量减
• vsubl_type:相减,结果向量长度扩大一倍
• vsubw_type:两个输入vector元素位数不一致,第一个vector宽度需大于第二个vector
• vsubhn_type:相减,结果向量长度缩短一半, r[i] = a[i] – b[i]
• vqsub_type: 饱和指令 r[i] = sat(a[i] – b[i])
• vhsub_type: 相减结果再除2。r[i] = (a[i] – b[i]) >> 1
• vrsubhn_type: 相减相减结果再加1,结果向量长度再缩短一半。r[i] = a[i] – b[i] + 1
倒数(用于代替除法)
• vrecpe_type: 求近似倒数,type是f32或者u32。 vrecpe_type计算倒数能保证千分之一左右的精度,如1.0的倒数为0.998047。
• vrecps_f32:两个vector乘积的倒数
• 高精度倒数,牛顿拉夫逊迭代
① //先执行vrecpe求出src的低精度倒数rec
rec = vrecpeq_f32(src)
② //使用vrecps,执行下句后能达到百万分之一左右精度结果recip,如1.0的倒数为0.999996
recip1 = vmulq_f32 (vrecpsq_f32 (src, rec), rec);
③ //再次执行vrecps后,能基本能达到完全精度,如1.0的倒数为1.000000
recip2 = vmulq_f32 (vrecpsq_f32 (src, recip1), recip1);
• vsqrt_type: 计算输入值的平方根,r[i]=sqrt(a[i])。输入、输出向量为整数时,结果为近似值
• vrsqrte_type: 计算输入值的平方根的倒数,r[i]=1/sqrt(a[i]) 。
乘法
• vmul_n_type: r[i] = a[i] * b
• vmul_lane_type: r[i] = a[i] * b[ilane]
• vmull_type: 变长乘法运算,为了防止溢出
• 乘加组合运算
vmla_type: r[i] = a[i] + b[i] * c[i]
vmla_n_type: r[i] = a[i] + b[i] * c
vmla_lane_type: r[i] = a[i] + b[i] * c[ilane]
vmlal_type: r[i] = a[i] + b[i] * c[i],先将b*c结果向量的bit长度增长,再与a相加
vfma_f32:r[i] = a[i] + b[i] * c[i] ,在加法之前,b[i] 、c[i]相乘的结果不会被四舍五入
fma和mla区别
mla:先做乘法,再做加法。相乘结果会有精度舍入。
fma:为浮点数乘加操作专门设计的融合乘积累加扩展指令,运算速度和精度更高!
• 乘减组合运算
vmls_type: r[i] = a[i] - b[i] * c[i]
vmls_n_type: r[i] = a[i] - b[i] * c
vmls_lane_type: r[i] = a[i] - b[i] * c[ilane]
vmlsl_type: r[i] = a[i] - b[i] * c[i] ,并将结果进行向量长度增长
vfms_f32:r[i] = a[i] - b[i] * c[i] , 在减法之前,bi、ci相乘的结果不会被四舍五入
比较操作
• 相等,equal to
vceq_type
• 大于, greater than
vcgt_type
• 小于 ,less than
vclt_type
• 大于或等于, greater than or equal to
vcge_type
• 小于或等于
vcle_type
• 比较指令特点: 结果只能为整型
比较结果为true,对应通道处元素值,置0xFF..FF
结果为false,对应通道处元素值,置0
• 利用条件判断结果进行选择
例如:
float32x4_t vbslq_f32(uint32x4_t a, float32x4_t b, float32x4_t c)
逻辑操作
• vand_type: 与,r[i] = a[i] & b[i]
• vorr_type: 或,r[i] = a[i] | b[i]
• veor_type:异或, 对应bit位相同则结果为0,否则为1
• vmvn_type: 非,r[i] = ~a[i] ,结合异或指令可以成为同或
其他操作
绝对值
vabs_type: r[i] = |a[i]|
vqabs_type: r[i] = sat(|a[i]|)
vabd_type: r[i] = |a[i] – b[i]|
vabdl_type: 长指令
vaba_type: r[i] = a[i] + |b[i] – c[i]|
vabal_type: 长指令
最大最小值
vmax_type: r[i] = a[i] >= b[i] ? a[i] : b[i]
vpmax_type: r[0] = a[0] >= a[1] ? a[0] : a[1], ..., r[4] = b[0] >= b[1] ? b[0]: b[1], ...
vmin_type: r[i] = a[i]<= b[i] ? a[i] : b[i]
vpmin_type: r[0] = a[0] <= a[1] ? a[0] : a[1], ..., r[4] = b[0] <= b[1] ? b[0] : b[1], ...
vmaxvq_type:单个向量中的最大值
vminvq_type:单个向量中的最小值
移位运算(只能操作整型)
• 左移
vshl_type: r[i] = a[i] << b[i] ,如果b[i]是负数,则变成右移
例uint16x4_t vshl_u16 (uint16x4_t a, int16x4_t b)
vshl_n_type: r[i] = a[i] << b
例uint16x4_t vshl_n_u16(uint16x4_t a, const int b)
• 右移
vshr_type: r[i] = a[i] >> b[i] ,如果b[i]是负数,则变成左移
vshr_n_type: r[i] = a[i] >> b
取相反数
vneg_type: r[i] = -a[i]
vqneg_type: r[i] = sat(-a[i])
数据类型转换(浮点型和整型之间,不同位数浮点型之间)
vcvt_type1_type2:将数据从type2转换成type1,注意,在float转换到uint时,是向下取整,且如果是负数,则转换后为0
获取、设置neon寄存器某个通道的值
• vget_low_type: 获取128bit vector的低半部分元素,输出的是元素类型相同的64bit vector
int8x8_t vget_low_s8(int8x16_t a)
• vget_high_type: 获取128bit vector的高半部分元素,输出的是元素类型相同的64bit vector
float16x4_t vget_high_f16(float16x8_t a)
• vget_lane_type: 获取元素类型为type的vector中指定的某个元素值。
uint8_t vget_lane_u8(uint8x8_t v, const int lane)
• vset_lane_type: 设置元素类型为type的vector中指定的某个元素的值,并返回新vector
uint8x8_t vset_lane_u8(uint8_t a, uint8x8_t v, const int lane)
点积
• uint32x4_t vdotq_u32(uint32x4_t c, uint8x16_t a, uint8x16_t b)
例如,对第一组四个元素执行的操作为:
c[0] = c[0] +((a[0] * b[0])+(a[1] * b[1] +(a[2] * b[2] +(a[3] * b[3]))