从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) / 4
            dst_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) / 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;
        }
    }
}

通过学习向量化编程,我们可知,数据的计算可以利用单指令多数据的方式进行加速,例如上面的例子中的内层循环,下面就使用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; // 偶数行进入下一个stride
            v8_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 leftover
        src_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编程过程中的优势以及将要面临的问题,主要是剩余数据处理的方式,后面将继续深入学习。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自建Web视频会议,视频互动,SFU/MCU融合架构选型方案分析

网络越来越好&#xff0c;大家已经越来越多接受在家在线办公&#xff0c;在线工作越来越离不开视频会议&#xff0c;视频互动&#xff0c;当然云平台很多&#xff0c;但也用不同的需求&#xff0c;很多需要自建平台与自已的业务系统集成对接。因为大家业务系统多是b/s架构。一般…

Flink StreamGraph生成过程

文章目录 概要SteramGraph 核心对象SteramGraph 生成过程 概要 在 Flink 中&#xff0c;StreamGraph 是数据流的逻辑表示&#xff0c;它描述了如何在 Flink 作业中执行数据流转换。StreamGraph 是 Flink 运行时生成执行计划的基础。 使用DataStream API开发的应用程序&#x…

远程调用--Http Interface

远程调用--Http Interface 前言1、导入依赖2、定义接口3 创建代理&测试4、创建成配置变量 前言 这个功能是spring boot6提供的新功能&#xff0c;spring允许我们通过自定义接口的方式&#xff0c;给任意位置发送http请求&#xff0c;实现远程调用&#xff0c;可以用来简化…

Android 开发环境搭建的步骤

本文将为您详细讲解 Android 开发环境搭建的步骤。搭建 Android 开发环境需要准备一些软件和工具&#xff0c;以下是一些基础步骤&#xff1a; 1. 安装 Java Development Kit (JDK) 首先&#xff0c;您需要安装 Java Development Kit (JDK)。JDK 是 Android 开发的基础&#xf…

跨平台指南:在 Windows 和 Linux 上安装 OpenSSL 的完整流程

Windows安装 一&#xff1a;找到安装包&#xff0c;双击即可 https://gitee.com/wake-up-again/installation-package.git 二&#xff1a;按照提示&#xff0c;一步一步来&#xff0c;就可以啦 三&#xff1a;此界面意思是&#xff0c;是否想向创作者捐款&#xff0c;自己视情…

Ubuntu20.04: UE4.27 中 Source Code 的编辑器下拉框没有 Rider选项

问题描述 最近想用 Rider 作为 UE4 开发的 IDE&#xff0c;但安装好 Rider 后&#xff0c;发现编辑器下拉框中没有 Rider 的选项&#xff0c;我检查了 UE4 的插件&#xff0c;发现 Rider Integration 插件已经安装且启用的。 环境&#xff1a;Ubuntu 20.04 UE4.27 Rider2023…

3、Redis Cluster集群运维与核心原理剖析

Redis集群方案比较 哨兵模式 在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态&#xff0c;如果master节点异常&#xff0c;则会做主从切换&#xff0c;将某一台slave作为master&#xff0c;哨兵的配置略微复杂&#xff0c;并且性能和高可用性…

企业计算机服务器中了360勒索病毒如何解密,360后缀勒索病毒处理流程

对于众多的企业来说&#xff0c;企业的数据是企业发展的核心&#xff0c;越来越多的企业开始注重企业的数据安全问题&#xff0c;但随着网络技术的不断发展与应用&#xff0c;网络黑客的攻击加密手段也在不断升级。近期&#xff0c;云天数据恢复中心接到多家企业的求助&#xf…

【深入理解设计模式】桥接设计模式

桥接设计模式 桥接设计模式是一种结构型设计模式&#xff0c;它旨在将抽象部分与实现部分分离&#xff0c;使它们可以独立变化&#xff0c;从而更好地管理复杂性。桥接模式通常涉及多个层次的抽象&#xff0c;其中一个层次&#xff08;通常称为"抽象"&#xff09;依…

YOLO-World 简单无需标注无需训练直接可以使用的检测模型

参考: https://github.com/AILab-CVC/YOLO-World YOLO-World 常规的label基本不用训练,直接传入图片,然后写入文本label提示既可 案例demo: 1)官方提供 https://huggingface.co/spaces/stevengrove/YOLO-World https://huggingface.co/spaces/SkalskiP/YOLO-World 检测…

javaWebssh在线授课辅导系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh在线授课辅导系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用 B/S模式开发。开发环境为TOMCAT7.…

Spring框架学习

Spring&#xff1a; &#xff08;1&#xff09;Bean线程安全问题 &#xff08;2&#xff09;AOP&#xff0c;事务原理&#xff0c;事务失败 &#xff08;3&#xff09;Bean的生命周期 &#xff08;4&#xff09;循环依赖 SpringMVC&#xff1a; &#xff08;1&#xff09…

技术小知识:面向对象和过程的区别 ⑤

一、思想区别 面相对象&#xff1a;始终把所有事情思考归类、抽离封装成对象来调用完成。 面向过程&#xff1a;直接平铺展开按顺序执行完成任务。 面向对象多了很多对象的创建、使用&#xff0c;销毁的过程资源消耗。是一种模块化编程思想。 https://www.cnblogs.com/kuangmen…

为何要使用流媒体服务器

安防系统中&#xff0c;我们偶尔会遇到“流媒体服务器”这个词&#xff0c;那么为什么需要这个服务呢&#xff1f; 视频监控 我们知道&#xff0c;监控摄像机的工作原理就是将自然界的光影&#xff0c;通过摄像机镜头对焦到“靶芯”&#xff08;CMOS&#xff09;&#xff0c;实…

mysql8修改密码

mysql8.0修改密码 windows下忘了MySQL8.0的密码&#xff0c;可以通过以下方式修改。 1、管理员方式打开cmd命令窗口 输入&#xff1a; net stop mysql接着输入&#xff1a; mysqld --console --skip-grant-tables --shared-memory2、管理员方式打开另外一个cmd窗口 输入&…

nvm安装和使用保姆级教程(详细)

一、 nvm是什么 &#xff1a; nvm全英文也叫node.js version management&#xff0c;是一个nodejs的版本管理工具。nvm和npm都是node.js版本管理工具&#xff0c;为了解决node.js各种版本存在不兼容现象可以通过它可以安装和切换不同版本的node.js。 二、卸载之前安装的node: …

c++之通讯录管理系统

1&#xff0c;系统需求 通讯录是一个记录亲人&#xff0c;好友信息的工具 系统中需要实现的功能如下&#xff1a; 1&#xff0c;添加联系人&#xff1a;向通讯录中添加新人&#xff0c;信息包括&#xff08;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;联系电话&#…

基于SpringBoot的企业头条管理系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

Libevent的使用及reactor模型

Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库&#xff0c;主要有以下几个亮点&#xff1a;事件驱动&#xff08; event-driven&#xff09;&#xff0c;高性能;轻量级&#xff0c;专注于网络&#xff0c;不如 ACE 那么臃肿庞大&#xff1b;源代码相当精炼、易读…

深入理解抽象工厂模式:原理、应用与优缺点分析

文章目录 **一、模式原理****二、使用场景****三、为何使用抽象工厂模式**四、代码示例**五、优点与缺点****总结** 一、模式原理 ​ 抽象工厂模式是一种创建型设计模式&#xff0c;其核心思想在于通过抽象工厂接口提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而不…