自学内容网 自学内容网

从0开始学习NEON(0)

1、前言

​ 最近在学习NEON指令集,主要学习"单指令流,多数据流"的编程方式,在这之前主要是针对cuda编程进行学习。最近的一部分工作转移到了arm主板上,接触到了 ncnn这个开源项目,不得不佩服nihui的强大。在学习ncnn的过程中了解到,arm下的模型加速主要用的技术就有NEON.因此,本着期待有一天能够熟读ncnn源码为目标,从0开始学习NEON!.

​ 关于NEON的介绍,这里不再赘述,网上有很多详细的文章,我是从下面这几篇文章入门的。

ARM SIMD 指令集:NEON 简介CPU 优化技术-NEON 指令介绍ARM NEON指令集总结, 本系列的文章主要是记录从0学习NEON中的一些知识点,便于往后复习,或者对刚学习NEON的同学有所帮助,如有不对的地方,还望指正。我的学习环境是一块国产主板,没有板子的同学可以使用NEON_2_SSE.h头文件在x86平台上进行仿真。

​ 由于本人是CV领域的,后面的学习都会在图像、深度学习部署方向进行,后续文章中还会汇总一些高质量的博客,便于参照学习,文章也许并不是循序渐进的,是在网上看到什么学习资料,就会耕畜相应的链接,并在本地进行学习或者复现。

2、学习记录
2.1、第一个例子
#include <stdio.h>
#include <stdlib.h>
// 包含NEON头文件
#include <arm_neon.h>
#include <math.h>

int main(){

    unsigned char a[8] = {0,1,2,3,4,5,6,7};
    unsigned char b[8] = {8,9,10,11,12,13,14,15};
    unsigned char c[8];
    
    // uint8x8_t uint8类型的数据,连续读取8个数据,
    uint8x8_t rega, regb, regc;

    // 从内存中读取8个8位数据到寄存器, vld1q_u8则表示从内存中读取16个8位数据到内存中
    // 'q'指明操作寄存器宽度,为'q'时操作QWORD, 为128位,未指明时操作寄存器为DWORD,为64位;
    rega = vld1_u8(&a[0]);  
    regb = vld1_u8(&b[0]);
    // 向量加法,将两个寄存器中的值相加,使用一条指令就可以并行实现8个加法,体现了单指令多数据的思想。
    regc = vadd_u8(rega, regb);  

    // 将寄存器中的值写回到内存中
    vst1_u8(&c[0], regc);
    for(int i=0;i<8;i++){
        printf("%d ", c[i]);
    }
    return 0;
}

通过上面的例子,NEON的基本步骤和cuda是一样的,先将数据搬运到寄存器(显存),进行并行计算,最后再把结果写回到内存中。

2.2、第二个例子
#include <stdio.h>
#include <stdlib.h>
#include <arm_neon.h>
#include <math.h>
#include <iostream>
using namespace std;
int main(){
    // 1、直接在q寄存器中初始化4个类型为float的值。
    float32x4_t  arr = vdupq_n_f32(1.0);   //0初始化 arr = {1.0, 1.0, 1.0, 1.0}

    // 2、累加和
    float arr[4] = {1.0, 2.0, 3.0, 4.0};
    float32x4_t arr = vld1q_f32(arr);   // 将数据加载到寄存器
    float ans = vaddvq_f32(arr);        // ans = 1+2+3+4 = 10
    return 0;
}
2.3、第三个例子

第三个例子来自于https://blog.csdn.net/weixin_40162153/article/details/129109774?spm=1001.2014.3001.5502,

在opencv以RGB的格式读取一张图片时,其在内存中的排列为 RGBRGBRGB…,有时候,我们想把每个通道值单独提取出来进行直方图均衡化或者其他操作,在c++中,我们可以得到如下的代码。

void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {
    for (int i=0; i < len_color; i++) {
        r[i] = rgb[3*i];
        g[i] = rgb[3*i+1];
        b[i] = rgb[3*i+2];
    }
}

而通过NEON加速,可以通过如下的代码进行。

void rgb_deinterleave_neon(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {
    int num8x16 = len_color / 16;  //使用128位的q寄存器进行操作,需要操作的次数
    uint8x16x3_t intlv_rgb;  // 数据格式为 uint8类型的数据,16*3组。每次使用3个q寄存器.每个寄存器分别储存R、G、B值。
    for (int i=0; i < num8x16; i++) {
     // rgb+3*16*i  step,每处理一次需要往后+多少个位置,
        intlv_rgb = vld3q_u8(rgb+3*16*i);  // vld3q_u8,从起始点位置 交织加载 16个RGB数据
        vst1q_u8(r+16*i, intlv_rgb.val[0]); // 将16个R数据写回到内存
        vst1q_u8(g+16*i, intlv_rgb.val[1]); // 将16个G数据协回到内存
        vst1q_u8(b+16*i, intlv_rgb.val[2]); // 将16个B数据协回到内存
    }
3、总结

通过阅读几篇入门文章,初步了解了NEON的加速机制,主要是如何利用好d、q寄存器。使得加载和处理数据的时候后能够占满寄存器,。后续应该还会遇到向量化剩余部分(leftovers)处理进行学习。


原文地址:https://blog.csdn.net/weixin_42108183/article/details/136412104

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!