• 大前端CPU优化技术--NEON intrinsics开篇


    前言

    本文继续分析NEON指令,上篇文章中我们浅析了arm指令集,指令格式相关的基础知识,本文会继续深入NEON intrinsics指令的相关内容和作用浅析,内容可能会有点多,为保证大家的学习效果和良好的阅读体验,准备分上下两篇文章讲解,请各位读者耐心阅读。

    intrinsics 指令介绍

    Load/Store

    以解交织的方式加载数据,将内存加载数据进neon寄存器

    1. // 以解交织方式加载数据到n个向量寄存器, n为1~4
    2. Result_t vld[n]_type(Scalar_t *p_addr);
    3. //用type类型的内存中第一个值,初始化第一个新vector的所有元素,用内存中第二个值,初始化第二个新vector的所有元素。
    4. vld2_dup_type

    以交织的方式存储数据,从neon寄存器加载数据进内存

    1. // 将n个寄存器的N通道数据以交织方式存储到内存中, n为1~4
    2. void vst[n]_lane_type(Scalar_t *p_addr, Vector_t M, int N);
    3. // 将元素类型为type格式的vector中指定的某个元素装入内存
    4. vst1_lane_type

    Arithmetic

    整数和浮点数的加减运算

    1. // 基本的加减操作
    2. Result_t vadd_type(Vector1_t N, Vector2_t M);
    3. Result_t vsub_type(Vector1_t N, Vector2_t M);
    4. // L(Long)类型的指令加减运算,输出向量长度是输入的两倍。
    5. Result_t vaddl_type(Vector1_t N, Vector2_t M);
    6. Result_t vsubl_type(Vector1_t N, Vector2_t M);
    7. // W(Wide)类型的指令加减运算,第一个输入向量的长度是第二个输入向量长度的两倍。
    8. Result_t vaddw_type(Vector1_t N, Vector2_t M);
    9. Result_t vsubw_type(Vector1_t N, Vector2_t M);
    10. // H(half)类型的加减运算;将计算结果除以2。
    11. Result_t vhadd_type(Vector1_t N, Vector2_t M);
    12. Result_t vhsub_type(Vector1_t N, Vector2_t M);
    13. // Q(Saturated)饱和类型的加减操作,结果超出元素的最大值时,元素就取最大值。
    14. Result_t vqadd_type(Vector1_t N, Vector2_t M);
    15. Result_t vqsub_type(Vector1_t N, Vector2_t M);
    16. // RH(Rounding Half)类型的加减运算
    17. Result_t vrhadd_type(Vector1_t N, Vector2_t M);
    18. Result_t vrhsub_type(Vector1_t N, Vector2_t M);
    19. // HN(half Narrow)类型的加减操作
    20. Result_t vaddhn_type(Vector1_t N, Vector2_t M);
    21. Result_t vsubhn_type(Vector1_t N, Vector2_t M);
    22. // RHN(rounding half Narrow)类型的加减操作
    23. Result_t vraddhn_type(Vector1_t N, Vector2_t M);
    24. Result_t vrsubhn_type(Vector1_t N, Vector2_t M);

    Multiply

    整型和浮点型的乘法运算, 参与计算的都是向量

    1. // 基本乘法操作
    2. Result_t vmul_type(Vector1_t N, Vector2_t M);
    3. // l(Long)类型的乘法操作,防止溢出
    4. Result_t vmull_type(Vector1_t N, Vector2_t M);
    5. // QDL(Saturated, Double, Long)类型的乘法操作,当结果溢出时,取饱和值
    6. Result_t vqdmull_type(Vector1_t N, Vector2_t M);
    7. // 基本的乘加和乘减操作
    8. Result_t vmla_type(Vector1_t N, Vector2_t M, Vector3_t P);
    9. Result_t vmls_type(Vector1_t N, Vector2_t M, Vector3_t P);
    10. // L(Long)类型的乘加和乘减操作
    11. Result_t vmlal_type(Vector1_t N, Vector2_t M, Vector3_t P);
    12. Result_t vmlsl_type(Vector1_t N, Vector2_t M, Vector3_t P);
    13. // QDL(Saturated, Double, Long)类型的乘加和乘减操作
    14. Result_t vqdmlal_type(Vector1_t N, Vector2_t M, Vector3_t P);
    15. Result_t vqdmlsl_type(Vector1_t N, Vector2_t M, Vector3_t P);
    16. // QDLH(Saturated, Double, Long, Half)类型的乘法操作
    17. Result_t vqdmulh_type(Vector1_t N, Vector2_t M);
    18. // QRDLH(Saturated, Rounding Double, Long, Half)类型的乘法操作
    19. Result_t vqrdmulh_type(Vector1_t N, Vector2_t M);

    带通道类型的乘法操作

    1. // 基本的乘法操作
    2. Result_t vmull_lane_type(Vector1_t N, Vector2_t M, int n);
    3. // 基本的乘加和乘减操作
    4. Result_t vmla_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
    5. Result_t vmls_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
    6. // L(long) 类型的乘加和乘减操作
    7. Result_t vmlal_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
    8. Result_t vmlsl_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
    9. // QDL(Saturated, Double, long) 类型的乘加和乘减操作
    10. Result_t vqdmlal_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
    11. Result_t vqdmlsl_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
    12. // QDH(Saturated, Double, Half) 类型的操作
    13. Result_t vqdmulh_lane_type(Vector1_t N, Vector2_t M, int n);
    14. vqdmull_lane_type
    15. vqdmulh_lane_type
    16. // QRDLH(Saturated, Rounding Double, Long, Half)类型的乘法操作
    17. vqrdmulhq_lane_type

    向量和标量的乘法

    1. // 基本的向量和标量的乘法
    2. Result_t vmul_n_type(Vector_t N, Scalar_t M);
    3. // L(Long) 类型的向量和标量的乘法
    4. Result_t vmull_n_type(Vector_t N, Scalar_t M);
    5. // QDL(Saturated, Double, long) 类型的向量和标量的乘法
    6. Result_t vqdmull_n_type(Vector_t N, Scalar_t M);
    7. // QDH(Saturated, Double, Half) 类型的向量和标量的乘法
    8. Result_t vqdmulh_n_type(Vector_t N, Scalar_t M);
    9. // QRDH(Saturated, Double, Half) 类型的向量和标量的乘法
    10. Result_t vqrdmulh_n_type(Vector_t N, Scalar_t M);
    11. // L(Long) 类型的乘加和乘减操作
    12. Result_t vmlal_n_type(Vector1_t N, Vector2_t M, Scalar_t P);
    13. Result_t vmlsl_n_type(Vector1_t N, Vector2_t M, Scalar_t P);
    14. // QDL(Saturated, Double, long) 类型的乘加和乘减
    15. Result_t vqdmlal_n_type(Vector1_t N, Vector2_t M, Scalar_t P);
    16. Result_t vqdmlsl_n_type(Vector1_t N, Vector2_t M, Scalar_t P);
    17. // QRDLH(Saturated, Rounding Double, Long, Half)类型的乘法操作
    18. Result_t vqrdmulhq_n_type(Vector1_t N, Vector2_t M, Scalar_t P);

     比较类型

    注: eq 表示相等, ge 表示大于或等于, gt 表示大于, le 表示小于或等于, lt 表示小于

    逻辑比较操作,比较结果为true,输出向量的对应通道将被设置为全 1,否则设置为全0  

    1. Result_t vceq_type(Vector1_t N, Vector2_t M);
    2. vceqq_type
    3. Result_t vcge_type(Vector1_t N, Vector2_t M);
    4. vcgeq_type
    5. Result_t vcle_type(Vector1_t N, Vector2_t M);
    6. vcleq_type
    7. Result_t vcgt_type(Vector1_t N, Vector2_t M);
    8. vcgtq_type
    9. Result_t vclt_type(Vector1_t N, Vector2_t M);
    10. vcltq_type

    向量的绝对值比较,比较结果为true时,输出向量对应通道将被设置为全1,否则设置为全0

    1. Result_t vcage_type(Vector1_t N, Vector2_t M);
    2. vcageq_f32
    3. Result_t vcale_type(Vector1_t N, Vector2_t M);
    4. vcaleq_f32
    5. Result_t vcalt_type(Vector1_t N, Vector2_t M);
    6. vcaltq_f32
    7. Result_t vcagt_type(Vector1_t N, Vector2_t M);
    8. vcagtq_f32

     逻辑类

    按位与\或\非\异或操作

    1. Result_t vand_type(Vector1_t N, Vector2_t M);
    2. Result_t vorr_type(Vector1_t N, Vector2_t M);
    3. Result_t vmvn_type(Vector_t N);
    4. Result_t veor_type(Vector1_t N, Vector2_t M);

    其他

    1. // 按通道做与操作,为 true 时,将输出向量对应通道设置为全 1,否则设置为全 0
    2. Result_t vtst_type(Vector1_t N, Vector2_t M);
    3. // M 作为 mask,标识是否对 N 做清零操作。当 M 中某位为 1, 则将 N 中对应位清零,ri = ~M & N
    4. Result_t vbic_type(Vector1_t N, Vector2_t M);
    5. // ri = ai | (~bi)
    6. Result_t vorn_type(Vector1_t N, Vector2_t M);
    7. // 按位选择,参数为(N, M, P)。P 作为 mask,按位 select。当 P 中某位是 1 时,将选择 N 中对应位作为输出,否则选择 M
    8. Result_t vbsl_type(Vector1_t N, Vector2_t M, Vector3_t P);

    数据类型转换

    浮点数之间的转化, 以及浮点类型与整数类型之间的转化

    1. // f32、u32、s32之间的转换。在f32转到u32时,是向下取整,且如果是负数,则转换后为0
    2. Result_t vcvt_type1_type2
    3. // 单精度浮点转化为整数类型
    4. Result_t vcvt_type_f32(Vector_t N);
    5. // 整数类型转化为单精度浮点
    6. Result_t vcvt_f32_type(Vector_t N);
    7. // f16转化为f32
    8. Result_t vcvt_f16_f32(Vector_t N);
    9. // f32转化为f16
    10. Result_t vcvt_f32_f16(Vector_t N);

     数据重排

    向量提取

    1. //取第2个输入vector的低n个元素放入新vector的高位,新vector剩下的元素取自第1个输入vector最高的几个元素(可实现vector内元素位置的移动)
    2. Result_t vext_type(Vector1_t N, Vector2_t M, int n);
    3. vextq_type

     查找操作

    1. //第二个vector是索引,根据索引去第一个vector(相当于数组)中搜索相应的元素,并输出新的vector,超过范围的索引返回的是0.
    2. Result_t vtbl[n]_type(Vector1_t N, Vector2_t M);
    3. // 根vtbl1_type功能一样,不过搜索到的元素是用来替换第一个vector中的元素,并输出替换后的新vector,当索引超出范围时,则不替换第一个vector中相应的元素。
    4. Result_t vtbx[n]_type(Vector1_t N, Vector2_t M, Vector3_t P);

    向量翻转

    1. Result_t vrev64_type(Vector_t N);
    2. Result_t vrev32_type(Vector_t N);
    3. Result_t vrev16_type(Vector_t N);
    4. vrev16_type 按照 16bit 为块,块内数据按照 8bit 为单位进行翻转。
    5. vrev32_type 按照 32bit 为块,块内数据按照 8bit,16bit 为单位进行翻转。
    6. vrev64_type 按照 64bit 为块,块内数据按照8bit, 16bit, 32bit为单位进行翻转。

     交叉和解交叉操作

    1. // 交织操作,将两个输入vector的元素通过交叉生成一个有两个vector的矩阵
    2. Result_t vzip_type(Vector1_t N, Vector2_t M);
    3. // 解交织操作,将两个输入vector的元素通过反交叉生成一个有两个vector的矩阵(通过这个可实现n-way 交织)
    4. Result_t vuzp_type(Vector1_t N, Vector2_t M);

    Shift

    立即数类型的位移

    1. // 基本的立即数左移和右移
    2. Result_t vshr_n_type(Vector_t N, int n);
    3. Result_t vshl_n_type(Vector_t N, int n);
    4. // R(rounding) 类型的右移操作
    5. Result_t vrshr_n_type(Vector_t N, int n);
    6. // QL(Saturated, long) 类型的右移操作
    7. Result_t vqshl_n_type(Vector_t N, int n);
    8. // 右移累加操作
    9. Result_t vsra_n_type(Vector1_t N, Vector2_t M, int n);
    10. // R(rounding) 类型的右移累加操作
    11. Result_t vrsraq_n_type(Vector1_t N, Vector2_t M, int n);
    12. // ri = N << n 输入vector是有符号,输出vector是无符号
    13. Result_t vqshlu_n_type(Vector_t N, int n);
    14. // Q(Saturated) 类型的左移操作,而且输入是有符号,输出是无符号的
    15. Result_t vqshluq_n_type(Vector_t N, int n);
    16. // N(Narrow) 类型的右移操作
    17. Result_t vshrn_n_type(Vector_t N, int n);
    18. // QN(Saturated, Narrow) 类型的右移操作, 而且输入是有符号,输出是无符号的
    19. Result_t vqshrun_n_type(Vector_t N, int n);
    20. // QRN(Saturated, Rounding, Narrow) 类型的右移操作, 而且输入是有符号,输出是无符号的
    21. Result_t vqrshrun_n_type(Vector_t N, int n);
    22. // QN(Saturated, Narrow) 类型的右移操作
    23. Result_t vqshrn_n_type(Vector_t N, int n);
    24. // RN(Rounding, Narrow) 类型的右移操作
    25. Result_t vrshrn_n_type(Vector_t N, int n);
    26. // QRN(Rounding, Rounding, Narrow) 类型的右移操作
    27. Result_t vqrshrn_n_type(Vector_t N, int n);
    28. // N(Narrow) 类型的左移操作
    29. Result_t vshll_n_type(Vector_t N, int n);

    非立即数类型的位移

    1. // 左移
    2. Result_t vshlq_type(Vector1_t N, Vector2_t M);
    3. // Q(Saturated) 类型的左移操作
    4. Result_t vqshl_type(Vector1_t N, Vector2_t M);
    5. // QR(Saturated, rounding) 类型的左移操作
    6. Result_t vrshl_type(Vector1_t N, Vector2_t M);

     移位并插入

    1. // 将向量 M 中各个通道先右移动 n 位, 然后将移动后元素插入到 N 对应的元素中,
    2. // 并保持 N 中每个元素的高 n 位保持不变
    3. Result_t vsri_n_type(Vector1_t N, Vector2_t M, int n);
    4. vsri_n_type:
    5. vsriq_n_type:
    6. // 将向量 M 中各个通道先左移动 n 位, 然后将移动后元素插入到 N 对应的元素中,
    7. // 并保持 N 中第每个元素的低 n 位保持不变
    8. Result_t vsli_n_type(Vector1_t N, Vector2_t M, int n);
    9. vsli_n_type:
    10. vsliq_n_type:

    总结

    本文主要归纳总结了 NEON intrinsics常用指令相关的知识,附有对应的解释,希望耐心读文章的你能有所收获,敬请期待下篇文章。

  • 相关阅读:
    自动驾驶行业源代码防泄漏解决方案
    本地连接服务器使用GPU训练模型
    MySQL高级篇知识点——主从复制
    Zookeeper:Mac通过Docker安装Zookeeper集群
    Vue 2 如何添加 register-service-worker 以实现缓存请求的目的
    455. 分发饼干
    黑群晖自动调整cpu模式
    语法基础(数组)
    这10款VS Code神仙插件,嵌入式程序员必备
    C++STL---->vector
  • 原文地址:https://blog.csdn.net/jh1988abc/article/details/126612050