NEON优化:ARM优化高频指令总结

NEON优化:ARM优化高频指令总结

    • 前言
    • 读写
    • 计算
    • 转换
    • 操作
    • 参考资料

NEON优化系列文章:

  1. NEON优化1:软件性能优化、降功耗怎么搞?link
  2. NEON优化2:ARM优化高频指令总结, link
  3. NEON优化3:矩阵转置的指令优化案例,link
  4. NEON优化4:floor/ceil函数的优化案例,link
  5. NEON优化5:log10函数的优化案例,link
  6. NEON优化6:关于交叉存取与反向交叉存取,link
  7. NEON优化7:性能优化经验总结,link
  8. NEON优化8:性能优化常见问题QA,link

本篇博客主要分享根据优化经验总结的高频NEON指令。

总体划分:

  • 读写:数据存取及寄存器读写
  • 计算:加减乘和乘加
  • 转换:位宽、类型、重解释
  • 操作:移位、比较、绝对值、最大值

前言


了解指令前,你需要知道什么是向量?什么是向量线?

  • 向量,就是指一个寄存器中同时存的不同值,比如float32x4_t类型,就是个4元素的向量。
  • 向量线,就是向量里的具体元素值。

随后,为便于理解指令意义,你需要知道指令格式规范是什么?

  • 指令样式:int16x4_t vqmovn_s32(int32x4_t a);

    • int16x4,表示返回的类型,int16表示16位整型,x4表示为4个元素的向量
    • vq中的v表示vector向量运算,q表示饱和运算,比如溢出就截断为最值
      • 而如vmulq_n_f32中,_前面的q是指满位宽128位运算
    • movn表示运算类型,是做位宽变换操作
    • s32表示操作对象为int32
  • 饱和运算:

    • 涉及字符位由宽变窄时,超过当前类型最大值时自动截断成该类型最值
  • 并行位数:

    • arm平台的NEON并行计算:最大支持128位
    • 指令中,类型符下划线前,加q为128位运算,不加为64位运算;
      • vld1q_f32,对应f32x4的类型,满128位
      • vld1_f32,对应f32x2的类型,仅64位
  • 涉及到有标量的运算,函数会有_n_作为标识

    • 涉及标量:float32x4_t vmulq_n_f32(float32x4_t a, float32_t b);
    • 不涉及标量:float32x4_t vmulq_f32(float32x4_t a, float32x4_t b);

读写


数据存取

  • 读数据指令:
    • 指令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

  • <=比较: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} // 按16位为1小组,内部以8位为元素逆序
    dst = vrev64_u8(src) --> dst = {8,7,6,5,4,3,2,1} // 按64位为1小组,内部以8位为元素逆序  
    

参考资料


  • ARM官方NEON指令集在线手册,

    https://developer.arm.com/architectures/instruction-sets/intrinsics/

  • 指令函数汇总,https://blog.csdn.net/hemmingway/article/details/44828303

  • 指令详解1,https://blog.csdn.net/waterhawk/article/details/83932098

  • 指令讲解2,https://blog.csdn.net/billbliss/article/details/78924636

  • 指令讲解3,https://blog.csdn.net/xiongtiancheng/article/details/77103810

  • 指令讲解4,https://blog.csdn.net/a568478312/article/details/80937797

你可能感兴趣的:(经验总结,开发语言,NEON指令,性能优化)