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 = 10return 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)处理进行学习。