从0开始学习NEON(1)

1、前言

在上个博客中对NEON有了基础的了解,本文将针对一个图像下采样的例子对NEON进行学习。

学习链接:CPU优化技术 - NEON 开发进阶

上文链接:https://blog.csdn.net/weixin_42108183/article/details/136412104

2、第一个例子

现在有一张图片,需要对UV通道的数据进行下采样,对于同种类型的数据,相邻的4个元素求和并求均值。示意图如下图所示:

在这里插入图片描述

假定图像数据的宽为16的整数倍,如果使用c++代码,可以写出下面的代码:

void DownscaleUv(uint8_t *src, uint8_t *dst, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{//遍历每一行的数据for (int32_t j = 0; j < dst_height; j++){	// 偶数行起始位置,uint8_t *src_ptr0 = src + src_stride * j * 2;// 奇数行起始位置uint8_t *src_ptr1 = src_ptr0 + src_stride;// 存储起始位置uint8_t *dst_ptr = dst + dst_stride * j;// 没一次循环计算没for (int32_t i = 0; i < dst_width; i += 2){// U通道 (u1 + u2 + u3 + u4) / 4dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;// V通道 (v1 + v2 + v3 + v4) / 4dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;}}
}

通过学习向量化编程,我们可知,数据的计算可以利用单指令多数据的方式进行加速,例如上面的例子中的内层循环,下面就使用NEON来试试吧。

3、第2个例子

为了进行向量化加速,首先需要将UV数据分离,将UV数据分离的操作在NEON中很容易进行, 使用vld2交织加载或者储存即可。对于每一行的数据,交织加载的示意图如下。
在这里插入图片描述

交织加载的基本原理是按照间隔挑选数据。交织加载的例子如下所示:

void DownscaleUvNeon()
{vector<uint8_t> data;  // UVUVUVUVUV...for(int i=0;i<32;i++){data.push_back(i);}// uint8_t *src_ptr0 = (uint8_t *)data.data(); // load 第一行的数据uint8x16x2_t src;src = vld2q_u8(src_ptr0);   // 交织读取 16 * 2 的数据,需要两个q寄存器。auto a = src_odd.val[0];   // 一行的U数据vector<uint8_t> show_data(16);vst1q_u8 (show_data.data(),a);   // 将U数据顺序储存到内存中// 打印for(auto n : show_data){cout <<  static_cast<int>(n) << endl;  // 0,2,4,6,...}   
}
4、第3个例子

对于下UV数据采样来说,在偶数行进行上面的交织加载,再在奇数行上进行同样的操作。奇数行和偶数行相应的数据进行相加再求平均,即可得到最后的结果。代码实现如下:

#include <arm_neon.h>
void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{//用于加载偶数行的源数据,2组每组16个u8类型数据,(16 * 8) * 2 = 128 * 128, 因此需要两个q寄存器。 uint8x16x2_t v8_src0;//用于加载奇数行的源数据uint8x16x2_t v8_src1;//目的数据变量,需要一个Q寄存器uint8x8x2_t v8_dst;//目前只处理16整数倍部分的结果int32_t dst_width_align = dst_width & (-16);  //  dst_width & (-16),最大能够整除16的数。//向量化剩余的部分需要单独处理int32_t remain = dst_width & 15;int32_t i = 0;//外层高度循环,逐行处理for (int32_t j = 0; j < dst_height; j++){//偶数行源数据地址uint8_t *src_ptr0 = src + src_stride * j * 2;//奇数行源数据地址uint8_t *src_ptr1 = src_ptr0 + src_stride;//目的数据指针uint8_t *dst_ptr = dst + dst_stride * j;//内层循环,一次16个u8结果输出for (i = 0; i < dst_width_align; i += 16){//提取数据,进行UV分离v8_src0 = vld2q_u8(src_ptr0); src_ptr0 += 32; // 偶数行进入下一个stridev8_src1 = vld2q_u8(src_ptr1);src_ptr1 += 32; // 奇数行行进入下一个stride//水平两个数据相加uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);//上下两个数据相加,之后求均值v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);//UV通道结果交织存储vst2_u8(dst_ptr, v8_dst);dst_ptr += 16;}//process leftovers......}
}
5、第4个例子

当图像的宽度不是16的整数倍,需要考虑结尾数据处理,按照链接里面的例子,可以分为以下几种。

1、 padding

​ 也就是将数据补齐到想要的长度,如下图所示,比如我这里需要操作 uint8x8_t的数据,但是我的数据长度只有5,可以将数据的长度填充至8。
在这里插入图片描述

2、Overlap

​ 也就是重复利用其中的某些数据,在不填充其他数据的情况下进行,如下图所示,当需要利用uint8x4_t来对下面的数据进行计算时,可以先将04加载到寄存器上,再将36加载到寄存器上操作。
在这里插入图片描述

常用第二种方法对结尾数据进行处理,那么图像下采样的数据代码可以写成:

#include <arm_neon.h>void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{uint8x16x2_t v8_src0;uint8x16x2_t v8_src1;uint8x8x2_t v8_dst;int32_t dst_width_align = dst_width & (-16); // 最大能够整除16的数。int32_t remain = dst_width & 15;             // 需要剩余处理的数据长度int32_t i = 0;for (int32_t j = 0; j < dst_height; j++){uint8_t *src_ptr0 = src + src_stride * j * 2;uint8_t *src_ptr1 = src_ptr0 + src_stride;uint8_t *dst_ptr = dst + dst_stride * j;// 处理完宽度为16的整数倍数据了for (i = 0; i < dst_width_align; i += 16){v8_src0 = vld2q_u8(src_ptr0);src_ptr0 += 32;v8_src1 = vld2q_u8(src_ptr1);src_ptr1 += 32;uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);vst2_u8(dst_ptr, v8_dst);dst_ptr += 16;}// process leftover// remain 剩余需要处理的数据长度if (remain > 0){// 从后往前回退一次向量计算需要的数据长度// 有部分数据是之前处理过的,这部分的数据在这里重复计算一次src_ptr0 = src + src_stride * (j * 2) + src_width - 32; src_ptr1 = src_ptr0 + src_stride;dst_ptr = dst + dst_stride * j + dst_width - 16;v8_src0 = vld2q_u8(src_ptr0);v8_src1 = vld2q_u8(src_ptr1);uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);vst2_u8(dst_ptr, v8_dst);}}
}

3、 single

将剩余的元素单独处理,就是将剩余的元素利用NEON的只加载一个元素的功能,不推荐使用,因为这里又可能for循环多次。

4、将剩余的元素当作标量处理

也就是将剩下的元素直接使用c语言编程的方式进行计算。

void DownscaleUvNeonScalar(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{uint8x16x2_t v8_src0;uint8x16x2_t v8_src1;uint8x8x2_t v8_dst;int32_t dst_width_align = dst_width & (-16);int32_t remain = dst_width & 15;int32_t i = 0;for (int32_t j = 0; j < dst_height; j++){uint8_t *src_ptr0 = src + src_stride * j * 2;uint8_t *src_ptr1 = src_ptr0 + src_stride;uint8_t *dst_ptr = dst + dst_stride * j;for (i = 0; i < dst_width_align; i += 16) // 16 items output at one time{v8_src0 = vld2q_u8(src_ptr0);src_ptr0 += 32;v8_src1 = vld2q_u8(src_ptr1);src_ptr1 += 32;uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);vst2_u8(dst_ptr, v8_dst);dst_ptr += 16;}//process leftoversrc_ptr0 = src + src_stride * j * 2;src_ptr1 = src_ptr0 + src_stride;dst_ptr = dst + dst_stride * j;for (int32_t i = dst_width_align; i < dst_width; i += 2){dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;}}
}
6、总结

本次学习中通过一个下采样的例子学习的NEON编程过程中的优势以及将要面临的问题,主要是剩余数据处理的方式,后面将继续深入学习。
/ 4;

        dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;}
}

}


#### 6、总结本次学习中通过一个下采样的例子学习的NEON编程过程中的优势以及将要面临的问题,主要是剩余数据处理的方式,后面将继续深入学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/717955.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

获取 Windows 通知中心弹窗通知内容(含工具汉化)

目录 前言 技术原理概述 测试代码和程序下载连接 本文出处链接&#xff1a;https://blog.csdn.net/qq_59075481/article/details/136440280。 前言 从 Windows 8.1 开始&#xff0c;Windows 通知现在以 Toast 而非 Balloon 形式显示&#xff08; Bollon 通知其实现在是应用…

在ubuntu上安装hadoop完分布式

准备工作 Xshell安装包 Xftp7安装包 虚拟机安装包 Ubuntu镜像源文件 Hadoop包 Java包 一、安装虚拟机 创建ubuntu系统 完成之后会弹出一个新的窗口 跑完之后会重启一下 按住首先用ctrlaltf3进入命令界面&#xff0c;输入root&#xff0c;密码登录管理员账号 按Esc 然后输入 …

数据结构常用的字符串函数(中英双释)

头文件&#xff1a;string.h 1.strchr const char * strchr ( const char * str, int character ); Locate first occurrence of character in string str C string. character Character to be located. Return Value A pointer to the first occurrence of character in s…

适用于恢复iOS数据的 10 款免费 iPhone 恢复软件

现在&#xff0c;您可以获得的 iPhone 的存储容量比大多数人的笔记本电脑和台式电脑的存储容量还要大。虽然能够存储数千张高分辨率照片和视频文件、安装数百个应用程序并随身携带大量音乐库以供离线收听固然很棒&#xff0c;但在一个地方拥有如此多的数据可能会带来毁灭性的后…

2.2_5 调度算法

文章目录 2.2_5 调度算法一、适用于早期的批处理系统&#xff08;一&#xff09;先来先服务&#xff08;FCFS&#xff0c;First Come First Serve&#xff09;&#xff08;二&#xff09;短作业优先&#xff08;SJF&#xff0c;Shortest Job First&#xff09;&#xff08;三&a…

SpringMVC总结

SpringMVC SpringMVC是隶属于Spring框架的一部分&#xff0c;主要是用来进行Web开发&#xff0c;是对Servlet进行了封装。 对于SpringMVC我们主要学习如下内容: SpringMVC简介 请求与响应 REST风格 SSM整合(注解版) 拦截器 SpringMVC是处理Web层/表现层的框架&#xff…

易语言源代码5000例

仅供学习研究交流使用 加群下载

探索MyBatis-Plus的高阶用法

引言 MyBatis-Plus 是 MyBatis 的增强工具包&#xff0c;提供了许多方便快捷的功能来简化开发&#xff0c;提高效率。除了基本的 CRUD 操作外&#xff0c;MyBatis-Plus 还提供了一些高级功能&#xff0c;本文将探讨 MyBatis-Plus 的高阶用法&#xff0c;帮助开发者更好地利用该…

Linux服务器搭建超简易跳板机连接阿里云服务器

简介 想要规范内部连接阿里云云服务器的方式&#xff0c;但是最近懒病犯了&#xff0c;先搞一个简易式的跳板机过渡一下&#xff0c;顺便在出一个教程&#xff0c;其他以后再说&#xff01; 配置方法 创建密钥 登录阿里云&#xff0c;找到云服务器ECS控制台&#xff0c;点击…

【小白友好】LeetCode 打家劫舍 III

https://leetcode.cn/problems/house-robber-iii/description/ 前言 建议还是先看看动态规划的基础题再看这个。动态规划是不刷题&#xff0c;自己100%想不出来的。 基础题&#xff1a; 23 小白想法 现在我们想遍历的数据结构不是数组了&#xff0c;而是一颗树。在树上的d…

C++递推

统计每个月兔子的总数 #include<bits/stdc.h> using namespace std; int n,sum0; void f(int); int main() {int a[1000];cin>>n;a[1]1;a[2]2;for(int i3;i<1000;i){a[i]a[i-1]a[i-2];}cout<<a[n];return 0; } void f(int n){}猴子吃桃子 #include<b…

2024年华为OD机试真题-电脑病毒感染-Python-OD统一考试(C卷)

题目描述: 一个局域网内有很多台电脑,分别标注为0 - N-1的数字。相连接的电脑距离不一样,所以感染时间不一样,感染时间用t表示。 其中网络内一个电脑被病毒感染,其感染网络内所有的电脑需要最少需要多长时间。如果最后有电脑不会感染,则返回-1 给定一个数组times表示一个…

在Spring Boot中如何实现异常处理?

在Spring Boot中&#xff0c;异常处理可以通过几种方式实现&#xff0c;以提高应用程序的健壮性和用户体验。这些方法包括使用ControllerAdvice注解、ExceptionHandler注解、实现ErrorController接口等。下面是一些实现Spring Boot异常处理的常用方法&#xff1a; 1. 使用Cont…

Git实战(2)

git work flow ------------------------------------------------------- ---------------------------------------------------------------- 场景问题及处理 问题1&#xff1a;最近提交了 a,b,c,d记录&#xff0c;想把b记录删掉其他提交记录保留&#xff1a; git reset …

【C++ 编程指南】

C 编程指南 ■ C环境安装■ C 基本语法■ 预定义宏■ # 和 ## 运算符■ C 引用■ C 命名空间■ 定义命名空间■ using 指令■ 嵌套的命名空间 ■ String类■ 类■ 类的static静态成员 ■ C 继承■ 继承类型 public、protected 或 private■ 访问控制和继承■ 多继承■ 数据抽象…

机器学习-面经

经历了2023年的秋招&#xff0c;现在也已经入职半年了&#xff0c;空闲时间将面试中可能遇到的机器学习问题整理了一下&#xff0c;可能答案也会有错误的&#xff0c;希望大家能指出&#xff01;另外&#xff0c;不论是实习&#xff0c;还是校招&#xff0c;都祝福大家能够拿到…

990-28产品经理:Different types of IT risk 不同类型的IT风险

Your IT systems and the information that you hold on them face a wide range of risks. If your business relies on technology for key operations and activities, you need to be aware of the range and nature of those threats. 您的IT系统和您在其中持有的信息面临…

数据结构c版(2)——二叉树

本章我们来了解一下二叉树这一概念。 目录 1.树概念及结构 1.1树的概念​​​​​​​ 1.2 树的特点&#xff1a; 1.3 树的相关概念 1.4 树的表示​​​​​​​ 1.5 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2.二叉树概念及结构 2.1概念 …

Qt 简约美观的动画 摆钟风格 第十季

&#x1f60a; 今天给大家分享一个摆钟风格的加载动画 &#x1f60a; 效果如下: 最近工作忙起来了 , 后续再分享其他有趣的加载动画吧. 一共三个文件 , 可以直接编译运行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <Q…

【C++】用文件流的put和get成员函数读写文件

题目 编写一个mycopy程序&#xff0c;实现文件复制的功能。用法是在控制台输入&#xff1a; mycooy 源文件名 目标文件名 参数介绍 m a i n main main 函数的参数有两个&#xff0c;一个int类型参数和一个指针数组。 a r g c argc argc 表示参数的个数。参数为void时 a r g …