前言

今天我们继续介绍NEON intrinsics的指令知识,上篇大前端CPU优化技术--NEON intrinsics开篇中已经介绍了部分指令的作用。本篇文章除了介绍指令还会附上场景示例,方便大家更深刻的理解,废话不多说我们继续前面的指令讲解。

intrinsics 指令介绍

初始化

//将一个64bit的数据装入vector中,并返回元素类型为type的vector。
Result_t vcreate_type(Scalar_t N);
//用类型为type的数值,初始化一个元素类型为type的新vector的所有元素。
Resutl_t vdup_type(Scalar_t N);
Result_t vmov_n_type(Scalar_t N);
Result_t vdup_n_type(Scalar_t N);Result_t vdupq_n_type(Scalar_t N);
Result_t vmovq_n_type(Scalar_t N);//用元素类型为type的vector的某个元素,初始化一个元素类型为type的新vector的所有元素
Result_t vdup<q>_lane_type(Vector_t N, int n);
vdupq_lane_type

Data processing

max\min操作

// 基本的 max, min
Result_t vmax<q>_type(Vector1_t N, Vector2_t M);
Result_t vmin<q>_type(Vector1_t N, Vector2_t M);// pairwise 类型的 max, min
Result_t vpmax_type(Vector1_t N, Vector2_t M);
Result_t vpmin_type(Vector1_t N, Vector2_t M);

绝对值

// 基本的绝对值计算
Result_t vabs<q>_type(Vector_t N);// 差的绝对值操作
Result_t vabd<q>_type(Vector1_t N, Vector2_t M);// L(Long)类型, 差的绝对值
Result_t vabdl_type(Vector1_t N, Vector2_t M);// 差的绝对值,并和另一个向量相加
Result_t vaba<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);// L(Long)类型, 差的绝对值,并和另一个向量相加, 输出是输入长度的两倍
Result_t vabal_type(Vector1_t N, Vector2_t M, Vector3_t P);

取反

// 基本的取反操作
Result_t vneg<q>_type(Vector_t N);// Q(Saturated)类型,带饱和的取反操作
Result_t vqneg<q>_type(Vector_t N);

按位统计 0 或 1 的个数

// 统计每个通道 1 的个数
Result_t vcnt<q>_type(Vector_t N);// 从符号位开始,统计每个通道中与符号位相同的位的个数,且这些位必须是连续的
Result_t vcls<q>_type(Vector_t N);// 从符号位开始,统计每个通道连续0的个数
Result_t vclz<q>_type(Vector_t N);

倒数

// 对每个通道近似求倒,f32或者u32
Result_t vrecpe<q>_type(Vector_t N);// 对每个通道使用 newton-raphson 求倒
Result_t vrecps<q>_type(Vector1_t N, Vector2_t M);

平方根倒数


// 对每个通道平方根近似求倒
Result_t vrsqrte<q>_type(Vector_t N);// 对每个通道使用 newton-raphson 平方根近似求倒
Result_t vrsqrts<q>_type(Vector1_t N, Vector2_t M);

向量赋值

// N(Narrow) 类型的赋值,取输入每个通道的高半部分,赋给目的向量
Result_t vmovn_type(Vector_t N);// L(long) 类型的赋值,使用符号拓展或者 0 拓展的方式,将输入通道的数据赋给输出向量
Result_t vmovl_type(Vector_t N);// QN(Saturated, Narrow) 类型的赋值,饱和的方式赋值,输出是输入宽度的两倍
Result_t vqmovn_type(Vector_t N);// QN(Saturated, Narrow) 类型的赋值,饱和的方式赋值,输出是输入宽度的两倍,而且输入为有符号数据,输出无符号
Result_t vqmovun_type(Vector_t N);

类型转换

重新解析

//将元素类型为type2的vector转换为元素类型为type1的vector。数据重新解析
Result_t vreinterpret<q>_DSTtype_SRCtype(Vector1_t N);

两个 64bit 向量组合成一个 128bit 向量

Result_t vcombine_type(Vector1_t N, Vector2_t M);

提取 128bit 向量的高半部分或则低半部分

//获取128bit vector的高半部分元素,输出的是元素类型相同的64bit vector。
Result_t vget_high_type(Vector_t N);
//获取128bit vector的低半部分元素,输出的是元素类型相同的64bit vector。
Result_t vget_low_type(Vector_t N);

统计

// 统计向量每个元素有多少bit位是1
Result_t vcnt_type:
Result_t vcls_type:
Result_t vclz_type:
Result_t vcntq_type:

NEON intrinsics应用

RGB 去隔行示例

一个 24 位 RGB 图像,其中图像是一个像素数组,每个像素都有一个红色、蓝色和绿色元素,RGB数据是交错的,我们希望对它们进行去插错,并将这些值放在单独的颜色数组。

int num8x16 = len_color / 16;
uint8x16x3_t intlv_rgb;
for (int i=0; i < num8x16; i++) {intlv_rgb = vld3q_u8(rgb+3*16*i);vst1q_u8(r+16*i, intlv_rgb.val[0]);vst1q_u8(g+16*i, intlv_rgb.val[1]);vst1q_u8(b+16*i, intlv_rgb.val[2]);
}

题解:

  • vld3q_u8:通过加载 3*16 字节内存的连续区域来返回uint8x16x3_t的函数。加载的每个字节都以交替模式放置在三个uint8x16_t数组之一。
  • vst1q_u8:将uint8x16_t存储在给定地址的函数

矩阵乘法

矩阵乘法是在许多数据密集型应用程序中执行的操作。矩阵乘法过程如下:

  • A- 在第一个矩阵中取一行

  • B- 使用第二个矩阵中的列执行此行的点积

  • C- 将结果存储在新矩阵的相应行和列中

4*4的矩阵

NEON代码使用内部函数将两个 4x4 矩阵相乘。由于我们要处理的数值数量很少且固定,所有这些值都可以同时放入处理器的 Neon 寄存器中,因此我们可以完全展开循环。

void matrix_mul_4x4_neon(float32_t *a, float32_t *b, float32_t *c)
{// these are the columns Afloat32x4_t A0;float32x4_t A1;float32x4_t A2;float32x4_t A3;// these are the columns Bfloat32x4_t B0;float32x4_t B1;float32x4_t B2;float32x4_t B3;// these are the columns Cfloat32x4_t C0;float32x4_t C1;float32x4_t C2;float32x4_t C3;A0 = vld1q_f32(a);A1 = vld1q_f32(a+4);A2 = vld1q_f32(a+8);A3 = vld1q_f32(a+12);// Zero accumulators for C valuesC0 = vmovq_n_f32(0);C1 = vmovq_n_f32(0);C2 = vmovq_n_f32(0);C3 = vmovq_n_f32(0);// Multiply accumulate in 4x1 blocks, i.e. each column in CB0 = vld1q_f32(b);C0 = vfmaq_laneq_f32(C0, A0, B0, 0);C0 = vfmaq_laneq_f32(C0, A1, B0, 1);C0 = vfmaq_laneq_f32(C0, A2, B0, 2);C0 = vfmaq_laneq_f32(C0, A3, B0, 3);vst1q_f32(c, C0);B1 = vld1q_f32(B+4);C1 = vfmaq_laneq_f32(C1, A0, B1, 0);C1 = vfmaq_laneq_f32(C1, A1, B1, 1);C1 = vfmaq_laneq_f32(C1, A2, B1, 2);C1 = vfmaq_laneq_f32(C1, A3, B1, 3);vst1q_f32(c+4, C1);B2 = vld1q_f32(b+8);C2 = vfmaq_laneq_f32(C2, A0, B2, 0);C2 = vfmaq_laneq_f32(C2, A1, B2, 1);C2 = vfmaq_laneq_f32(C2, A2, B2, 2);C2 = vfmaq_laneq_f32(C2, A3, B2, 3);vst1q_f32(c+8, C2);B3 = vld1q_f32(b+12);C3 = vfmaq_laneq_f32(C3, A0, B3, 0);C3 = vfmaq_laneq_f32(C3, A1, B3, 1);C3 = vfmaq_laneq_f32(C3, A2, B3, 2);C3 = vfmaq_laneq_f32(C3, A3, B3, 3);vst1q_f32(c+12, C3);
}

题解:

  • float32x4_t:由4个 32 位浮点数组成的数组。
  • vld1q_f32:将4个 32 位浮点数加载到float32x4_t中的函数。
  • vfmaq_lane_f32:使用融合乘法累加指令的函数。将float32x4_t值乘以另一个float32x4_t元素,然后将结果加第三个float32x4_t,然后再返回结果。
  • vst1q_f32:将float32x4_t存储在给定地址的函数。

对于更大的矩阵,我们可以以4*4作为矩阵块来乘,但是需要0填充矩阵。下面来个更通用的矩阵乘法,代码如下:

void matrix_multiply_neon(float32_t  *a, float32_t  *b, float32_t *c,uint32_t n, uint32_t m, uint32_t k)
{/* * Multiply matrices A and B, store the result in C. * It is the user's responsibility to make sure the matrices are compatible.*/     int A_idx;int B_idx;int C_idx;// these are the columns of a 4x4 sub matrix of Afloat32x4_t A0;float32x4_t A1;float32x4_t A2;float32x4_t A3;// these are the columns of a 4x4 sub matrix of Bfloat32x4_t B0;float32x4_t B1;float32x4_t B2;float32x4_t B3;// these are the columns of a 4x4 sub matrix of Cfloat32x4_t C0;float32x4_t C1;float32x4_t C2;float32x4_t C3;for (int i_idx=0; i_idx<n; i_idx+=4 {for (int j_idx=0; j_idx<m; j_idx+=4){// zero accumulators before matrix opC0=vmovq_n_f32(0);C1=vmovq_n_f32(0);C2=vmovq_n_f32(0); C3=vmovq_n_f32(0);for (int k_idx=0; k_idx<k; k_idx+=4){// compute base index to 4x4 blocka_idx = i_idx + n*k_idx;b_idx = k*j_idx k_idx;// load most current a values in rowA0=vld1q_f32(a+A_idx);A1=vld1q_f32(a+A_idx+n);A2=vld1q_f32(a+A_idx+2*n);A3=vld1q_f32(a+A_idx+3*n);// multiply accumulate 4x1 blocks, i.e. each column CB0=vld1q_f32(b+B_idx);C0=vfmaq_laneq_f32(C0,A0,B0,0);C0=vfmaq_laneq_f32(C0,A1,B0,1);C0=vfmaq_laneq_f32(C0,A2,B0,2);C0=vfmaq_laneq_f32(C0,A3,B0,3);B1=v1d1q_f32(b+B_idx+k);C1=vfmaq_laneq_f32(C1,A0,B1,0);C1=vfmaq_laneq_f32(C1,A1,B1,1);C1=vfmaq_laneq_f32(C1,A2,B1,2);C1=vfmaq_laneq_f32(C1,A3,B1,3);B2=vld1q_f32(b+B_idx+2*k);C2=vfmaq_laneq_f32(C2,A0,B2,0);C2=vfmaq_laneq_f32(C2,A1,B2,1);C2=vfmaq_laneq_f32(C2,A2,B2,2);C2=vfmaq_laneq_f32(C2,A3,B3,3);B3=vld1q_f32(b+B_idx+3*k);C3=vfmaq_laneq_f32(C3,A0,B3,0);C3=vfmaq_laneq_f32(C3,A1,B3,1);C3=vfmaq_laneq_f32(C3,A2,B3,2);C3=vfmaq_laneq_f32(C3,A3,B3,3);}//Compute base index for storesC_idx = n*j_idx + i_idx;vstlq_f32(c+C_idx, C0);vstlq_f32(c+C_idx+n,Cl);vstlq_f32(c+C_idx+2*n,C2);vstlq_f32(c+C_idx+3*n,C3);}}
}

边缘处理

处理图像边缘时,经常会有使用常数填充边界的情况。

NEON 开发中,可以使用DUP指令用数据初始化向量,然后使用EXT指令提取数据组建新向量。

// 构造边界填充向量
uint8_t a_0 =0;
uint8x8_t b_c0 = v_dup_n_u8(a_0);// 构建a_1
uint8x8_t a_1 = vext_u8(b_c0, a_0, 5)// 使用 vext 构建边界向量,a0 表示从纵坐标为 0 起始的向量
uint8x8_t c_border = vext_u8(a_1, b_c0, 3)

 注:EXT指令还常常用于滤波向量的重组操作。

总结

本文我们完结了NEON intrinsics指令知识的浅析,希望对大家在工作场景有抛砖引玉的作用,但是性能调优是个日积月累,持续反馈的过程,后面我们会继续介绍NEON 汇编开发和NEON性能调优进阶,敬请期待。

大前端CPU优化技术--NEON intrinsics进阶相关推荐

  1. 大前端CPU优化技术--NEON指令介绍

    前言 ARM NEON 可以提升音视频,图像,计算机视觉等计算密集型程序的性能,在上一篇大前端CPU优化技术--NEON技术的介绍中,我们知道一些编译器可以将 C/C++ 代码自动转换为 NEON 指 ...

  2. 大前端CPU优化技术--NEON技术

    前言 在上一篇中我们讲了SIMD技术的基础和前世今生,可以结合上一篇文章一起看大前端CPU优化技术--SIMD技术.今天我们全局性地讲解下NEON技术​. 目前主流的移动设备以ARM v7和v8版本架 ...

  3. 大前端CPU优化技术--NEON自动向量化

    前言 ARM NEON技术是一种高级的单指令多数据的架构扩展的实现.它是一种64位和128位混合的SIMD技术,主要应用场景是音视频处理,图像视觉计算,信号处理应用等需要密集计算的场景. NEON技术 ...

  4. 大前端CPU优化技术--NEON编程优化技巧

    前言 在前面的文章中我们介绍了NEON的基础,NEON技术的全景,指令及NEON intrinsic指令,相信大家能通过前面的学习写一些简单的NEON程序.但要想写好一个性能高的NEON程序,远不止你 ...

  5. 浅谈大前端的代表技术及其影响,值得我们思考

    到底哪些是大前端的代表技术?从业务上来说,我认为终端 开发.网关设计.接口设计.桌面端的 工程化都可以算是大前端的业务范畴. 具体的技术,则是基于 HTML5.NodeJS 的通用技术,以及各平台的专 ...

  6. 360前端星计划--技术翻译:进阶的直梯

    文学翻译 非文学翻译 艺术成分多一些 科学成分多一些 需要更多的灵感 需要更多的勤奋 责任小一些 责任大一些 技术翻译的意义 翻译技术文章,学习新技术思想 翻译技术文档,掌握标准和工具 翻译技术图书, ...

  7. 360前端星计划—技术翻译:进阶的直梯(李松峰)

    1. 翻译类型 文学翻译和非文学翻译 文学翻译 非文学翻译 艺术成分多一些 科学成分多一些 需要更多的灵感 需要更多的勤奋 责任小一些 责任大一些 2. 技术翻译的意义 翻译技术文章,学习新技术思想 ...

  8. 前端SEO优化技术汇总

    一.title.alt.h1 title: 网站头部标签<head>下的title,网站名称 备注:这里为什么不说标签中的title属性,,虽然鼠标上移可以显示图片名字,但是它跟SEO没一 ...

  9. 前端18个月难度翻番?来这里把握大前端技术本质进展丨稀土开发者大会

    图片来源:pexels.com "别更新了,学不动了"向来是前端开发群体的切肤之痛: React 还没学明白,Vue 就出来了: Vue 2.0 还没上手,3.0 就发布了: No ...

最新文章

  1. Active Diretory 全攻略(二)--AD与域
  2. c++派生类和基类的构造函数和析构函数
  3. python前端学习-------Flask框架基础(建议收藏)
  4. 如何测量代码执行时间
  5. centos 安装mysql
  6. NullPointerException
  7. axure类型app项目rp文件_Python编程快速上手实践项目--选择性拷贝指定类型文件到目的目录...
  8. 【Linux网络编程】浅谈 C/S 和 B/S 架构
  9. xaml中的布局面板
  10. python 交互式可视化库_Python 交互式可视化库
  11. Bootstrap3 地址元素样式
  12. 马云又出金句:文凭只是学费的收据,真正的文凭是生活中奋斗来的
  13. 国产13.56MHz读写器芯片Ci521替代兼容CV520
  14. 当BTC大空头遇上PlusToken,投资竟然成为一门玄学?
  15. HTML 编码规范之布尔型属性
  16. 别让失败阻碍了你成功的路
  17. docker命令讲解
  18. ux设计_如何草绘UX文章
  19. MySqlException(0x80004005) 报错
  20. 2023美国大学生数学建模竞赛Y题思路解析

热门文章

  1. 【Linux】共享内存
  2. Android输入法方法,android输入法–InputMethodManager
  3. 2020前端工程师的发展前景
  4. 北京市社会保险网上服务平台_城镇职工用户登陆_注册手机号更改
  5. 深入理解iOS App的启动过程
  6. 如何修改视频尺寸而不让画面变形?
  7. intellidea写java_用IntellIDEA开发JSP的一些总结 | 学步园
  8. LeetCode 831. Masking Personal Information【字符串,正则表达式】中等
  9. 石家庄科技工程职业学院计算机系,石家庄科技工程职业学院学生社团
  10. 网上看到的一个好文章,自勉