本篇博客主要分享根据优化经验总结的高频NEON指令。
总体划分:
- 读写:数据存取及寄存器读写
- 计算:加减乘和乘加
- 转换:位宽、类型、重解释
- 操作:移位、比较、绝对值、最大值
前言
了解指令前,你需要知道什么是向量?什么是向量线?
- 向量,就是指一个寄存器中同时存的不同值,比如float32x4_t类型,就是个4元素的向量。
- 向量线,就是向量里的具体元素值。
随后,为便于理解指令意义,你需要知道指令格式规范是什么?
读写
数据存取
- 读数据指令:
- 指令1:vld1q_f32(float p), 读满维128位数据,324,4个32位float数据
- 指令2:vld2q_f32(float p), 读2个满维128位数据,324*2
- 指令3:vld4q_f32(float p), 读4个满维128位数据,324*4
- 效果:从内存读取数据到NEON寄存器中
- 写数据指令:
- 指令1:void vst1q_f32(__transfersize(4) float32_t * ptr, float32x4_t val); // 拷贝4个32位的浮点,共128位
- 指令2:void vst1q_s16(__transfersize(8) int16_t * ptr, int16x8_t val); // 拷贝8个16位的整型,共128位
- 指令3:void vst1_s16(__transfersize(4) int16_t * ptr, int16x4_t val); // 拷贝4个16位的整型,共 64
- 效果:将NEON寄存器的值存入到内存(正常浮点变量),存储NEON向量到内存 store
在向量内设置向量线
- 指令:float32x4_t vdupq_n_f32(float32_t value);
- 效果:将所有向量线设置为相同的值,将向量初始为特定相同值
- 注意:进阶指令可调具体位置去设定相应值
取向量中的向量线
- 取前两个向量线指令:float32x2_t vget_low_f32(float32x4_t a); // a1, a2, a3, a4 => a1, a2
- 取后两个向量线指令:float32x2_t vget_high_f32(float32x4_t a); // a1, a2, a3, a4 => a3, a4
- 效果:取向量中的部分对,从4个值中取前两个与后两个
计算
加法
- 指令:int32x4_t vaddq_s32(int32x4_t a, int32x4_t b);
- 效果:vr = a + b
减法
- 指令:int32x4_t vsubq_s32(int32x4_t a, int32x4_t b);
- 效果:vr = a - b
向量乘标量
- 指令:float32x4_t vmulq_n_f32(float32x4_t a, float32_t b); // a1, a2, a3, a4;
- 效果:输出为
vr = (a1, a2, a3, a4) * b
向量与标量乘后加
- 指令:float32x4_t vmlaq_n_f32(float32x4_t a, float32x4_t b, float32_t c);
- 效果:向量与标量进行的乘加,结果为
vr = a + b * c - 特别注意:不是 a * b + c
转换
位宽转换
- 窄到宽指令:int32x4_t vmovl_s16(int16x4_t a);
- 效果:把4个16位数值扩展为4个32位的数值,相当于:
int16_t a = 3; int32_t b = (int32_t)a; - 宽到窄指令:int16x4_t vqmovn_s32(int32x4_t a);
- 效果:由宽字符到窄字符,由于可能溢位,故要做饱和运算
类型转换
- 指令:float32x4_t vcvtq_f32_s32(int32x4_t a);
- 效果:将32位整型转换为32位浮点,cvt是convert的缩写
类型重解释
- 指令:int8x16_t vreinterpretq_s8_f32(float32x4_t a); // 将float32x4的a解释成int8x16_t类型
- 效果:向量重新解释类型转换运算
- 说明:不更改值本身,将原始二进制值按不同类型进行解码
操作
左右移位
- 左移指令:uint32x4_t vshlq_n_u32(uint32x4_t a, __constrange(0,31) int b); // 左移,b范围:[0, 31]
- 右移指令:uint32x4_t vshrq_n_u32(uint32x4_t a, __constrange(1,32) int b); // 右移,b范围:[1, 32]
- 效果:向量按常数标量左右移位
差值绝对值
- 指令:float32x4_t vabdq_f32(float32x4_t a, float32x4_t b);
- 效果:vr = |a - b|
- 说明:能直接和标量进行运算的,只有乘法;其他如最大值,加减都不行
最大值
- 指令:float32x4_t vmaxq_f32(float32x4_t a, float32x4_t b); // a1, a2, a3, a4; b1, b2, b3, b4;
- 效果:成对取最大值,输出值为:
[max(a1, b1), max(a2, b2), ..., max(a4, b4)]
折叠最大值
- 指令:float32x2_t vpmax_f32(float32x2_t a, float32x2_t b); // a1, a2; b1, b2;
- 效果:取相零对的最大值,输出值为:
[max(a1, a2), max(b1, b2)]
比较大小
-
小于比较:uint32x4_t vcltq_f32(float32x4_t a, float32x4_t b); // 判断a<b -
<=比较 :uint32x4_t vcleq_s32(int32x4_t a, int32x4_t b); // 判断a<=b -
>=比较 :uint32x4_t vcgeq_s32(int32x4_t a, int32x4_t b); // 判断a>=b -
缩略助记:clt(compare less than),cgt(compare grete than),ceq(comprae equal), ge(>=), le(<=) -
返回类型:无符号数,位宽与入参相同
按位选择
- 指令:int32x4_t vbslq_s32(uint32x4_t a, int32x4_t b, int32x4_t c);
- 函数用法:将a的每一位进行判断,若是1,则输出b中对应位;否则输出c中对应位
- 注意事项:通常和比较函数的输出a结合使用,a为无符号比较结果,返回值类型与输入值b/c类型相同
向量内元素反转
-
指令:uint8x8_t vrev16_u8(uint8x8_t vec); -
助记符:vrev(bit)_(type) -
效果:向量内按指定的位数bit,成对相互交换 -
举例: uint8x8_t src = {1,2,3,4,5,6,7,8};
dst = vrev16_u8(src) --> dst = {2,1,4,3,6,5,8,7}
dst = vrev64_u8(src) --> dst = {8,7,6,5,4,3,2,1}
参考资料
|