本文主要是介绍NEON 内嵌函数梳理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一、介绍
- 1. 简介
- 2. 应用特点
- 3. NEON寄存器
- 4. Neon intrinsics
- 二、使用方法
- 1. NEON数据类型
- 2. 操作函数
- 3. 案例
- 3.1 RGB 存储
- 3.1.1 C 语言写法
- 3.1.2 Neon写法
- 3.1.3 效果对比
- 3.2 地图加载
- 3.2.1 C 语言写法
- 3.2.2 Neon写法
- 3.2.3 效果对比
- 三、注意事项
- 附录
- rgb_neon.cpp
- map_neon.cpp
一、介绍
1. 简介
ARM NEON 是适用于 ARM Cortex-A 和 Cortex-R 系列处理器的一种 SIMD(Single Instruction Multiple Data)扩展架构。SIMD 采用一个控制器来控制多个处理器,同时对一组数据中的每个数据分别执行相同操作,从而实现并行技术。
NEON 使用一组独立的寄存器,每个寄存器可以一次加载多个数据。NEON的计算仍然使用cpu,但是可以在多个操作数上同时执行,利用向量并行性实现高效的向量计算。
2. 应用特点
适用于使用大量重复计算的场景,实质是通过数据并行来提高执行效率。
3. NEON寄存器
NEON 寄存器主要是用来存放包含相同数据类型元素的向量,分为2种: Q寄存器(128位)和D寄存器(64位)。
ARMv7 :16个128位寄存器(Q 寄存器),每个Q寄存器又可以分成2个64位寄存器,即一共有32个64位寄存器( D 寄存器)。
在ARMv8 架构中寄存器的数量相比 ARMv7 架构数量翻倍。
NEON寄存器处理的数据需要对齐,不对齐会降低执行速度,甚至出错。
4. Neon intrinsics
ARM 平台提供了四种使用 NEON 技术的方式,分别为 NEON 内嵌函数(intrinsics)、NEON 汇编、NEON 开源库和编译器自动向量化。其中,NEON 内嵌函数相对容易上手操做。
- NEON 内嵌函数:类似于普通函数调用,简单易维护,编译器负责将 NEON 指令替换成汇编语言的复杂任务,主要包括寄存器分配和代码调度以及指令集重排,来达到获取最高性能的目标;
- NEON 汇编:汇编语言相对晦涩难懂,移植较难、不便于维护,但其效率最高;
- NEON 开源库:如 Ne10、OpenMAX、ffmpeg、Eigen3 和 Math-neon 等;
- 编译器自动向量化:目前大多数编译器都具有自动向量化的功能,将 C/C++ 代码自动替换为 SIMD 指令;
二、使用方法
1. NEON数据类型
Neon内嵌函数需要使用特定的数据类型来加载数据到寄存器。
格式: type
+size
+x+num
+_t
- type:数据类型,
int
、uint
、float
; - size:元素数据长度,整型长度为
8
、16
、32
、64
,浮点型为32
; - num:元素个数;
例子: uint32x4_t
为由4个32位无符号整型组成的128位寄存器,即每个存入的数据是32位无符号整型
2. 操作函数
接口查询地址
格式: v+mod
+opname
+shape
+flags
+_type
- mod:
- q 饱和计算(避免计算结果超出范围溢出);
- h 计算的结果再除以2;
- d 计算结果再x2;
- r 计算结果四舍五入;
- opname:
vadd
(加法),vsub
(减法),vmul
(乘法),vceq
(比较),vmax
(最大),vmin
(最小),ld
(加载到寄存器),st
(从寄存器读出数据) - shape:
- l 长指令,生成的元素是操作元素宽度的2倍;
- w 宽指令,1个双字向量和1个四字向量生成一个四字向量的元素;
- hn 窄指令,生成的元素是操作元素宽度的一半;
- flags: 默认:使用d寄存器(64位) q:使用q寄存器(128位);
例如:
uint16x4_t vhadd_u16(uint16x4_t a,uint16x4_t b) :2个uint16x4相加变成1个uint16x4,然后结果除以2
uint16x8_t vaddq_u16(uint16x8_t a,uint16x8_t b) :2个uint16x8相加变成1个uint16x8,q代表使用128位q寄存器来存储a和b及结果
uint32x4_t vaddl_u16(uint16x4_t a,uint16x4_t b):2个uint16x4相加变成1个uint32x4的数,l代表长指令
uint32x4_t vaddw_u16(uint32x4_t a,uint16x4_t b):1个uint32x4加1个uint16x4变成1个uint32x4
uint8x8_t vaddhn_u16(uint16x8_t a,uint16x8_t b):2个uint16x8相加变成1个uint8x8
uint8x16x3_t vld3q_u8 加载8x16x3个数据到3个q寄存器中
vst1q_u8 将8x16的数据读出(1个q寄存器)
3. 案例
3.1 RGB 存储
3.1.1 C 语言写法
void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {/** Take the elements of "rgb" and store the individual colors "r", "g", and "b".*/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];}}
3.1.2 Neon写法
#include "opencv2/opencv.hpp"// NEON version
// 函数作用:
// 输入 rgb 数组指针rgb 及长度 len_color
// 返回 三通道数组指针 r g bvoid rgb_deinterleave_neon(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {/** Take the elements of "rgb" and store the individual colors "r", "g", and "b"*/int num8x16 = len_color / 16;uint8x16x3_t intlv_rgb;for (int i=0; i < num8x16; i++) {intlv_rgb = vld3q_u8(rgb+3*16*i);//将rgb中的3*16个uint8数据依次存入3个q寄存器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]);}}
使用说明:
- int num8x16 = len_color / 16; 寄存器容量128位, 单通道一个数据为8位,填满该寄存器需16个数据;
- intlv_rgb = vld3q_u8(rgb+3*16*i) : 注意
vld3q_u8
中ld
类似表示从内存加载load
到寄存器,此处,按移位的方法将rgb
数据赋值给变量intlv_rgb
; - vst1q_u8(r+16*i, intlv_rgb.val[0]): 注意
vst1q_u8
中st
类似表示从寄存器写入内存,此处,将intlv_rgb.val[0]
中数据赋值给通道r
3.1.3 效果对比
一个1920*1080图片的rgb数据拆分时间如下:
2023-09-21 15:48:31.509 map[205177]: [205177][D][/home/gene/map/main.cpp:75] rgb_deinterleave_c function took 0.019974 seconds to execute.
2023-09-21 15:48:31.520 map[205177]: [205177][D][/home/gene/map/main.cpp:92] rgb_deinterleave_neon function took 0.009386 seconds to execute.
3.2 地图加载
需求:
map节点 建图时调用的contour_MowerMapContour_to_cvPoints 函数中会做世界(vslam)坐标 --> 像素(珊格)坐标的转换,有重复性的对每个传入的坐标点的x和y坐标值分别做与常量的计算转换。这可以使用Neon内联函数做并行操作。
3.2.1 C 语言写法
for (int i = 0; i < input_vslam_contour_size; i+=3){bool result = point_world_to_pixel(input_world_contour.navi_contour_points[i],input_world_contour.navi_contour_points[i + 1],GRID_ROWS/2, GRID_COLS/2,GRID_ROWS-1,0,map_new_center_x,map_new_center_y,row,col);}bool point_world_to_pixel(const float x,const float y,const int pixel_map_center_x,const int pixel_map_center_y,const int pixel_map_max,const int pixel_map_min,const double world_map_center_x,
const double world_map_center_y,int &row,int &col)
{row = int(round((x - world_map_center_x) * 20) + pixel_map_center_x);col = int(round((y - world_map_center_y) * 20) + pixel_map_center_y);...
}
3.2.2 Neon写法
row = int(round((x - world_map_center_x) * 20) + pixel_map_center_x);col = int(round((y - world_map_center_y) * 20) + pixel_map_center_y);
仅针对 C 语言版本如上代码的功能复现
const float32x4_t mulFactor = vdupq_n_f32(20.0);
const float32x4_t world_map_center_x_4 = vdupq_n_f32(map_new_center_x);
const float32x4_t world_map_center_y_4 = vdupq_n_f32(map_new_center_y);
const int32x4_t pixel_map_center_x_4 = vdupq_n_s32(GRID_ROWS/2);
const int32x4_t pixel_map_center_y_4 = vdupq_n_s32(GRID_COLS/2);
int num_point= input_vslam_contour_size / 3; // 坐标个数
int pixel_map_max = GRID_ROWS-1;
int pixel_map_min = 0;
int num32x4 = num_point / 4; //4个坐标为一组
float32x4x3_t intlv_xyz; //3个q寄存器分别存储x,y,z,一次处理3x4x32个元素for (int i=0; i < num32x4; i++) {intlv_xyz = vld3q_f32(input_world_contour.navi_contour_points.data()+3*4*i); //将rgb中的3*4个float数据依次存入3个q寄存器// 计算 row = int(round((input_world_contour.navi_contour_points[i] - world_map_center_x) * 20) + pixel_map_center_x);float32x4_t sub_1 = vsubq_f32(intlv_xyz.val[0], world_map_center_x_4); //x-world_map_center_xfloat32x4_t mul_1 = vmulq_f32(sub_1, mulFactor);//乘以20float32x4_t add_1 = vaddq_f32(mul_1, vreinterpretq_f32_s32(pixel_map_center_x_4));//+pixel_map_center_xint32x4_t row_4 = vcvtq_s32_f32(add_1);//float32x4转换成int32x4float32x4_t sub_2 = vsubq_f32(intlv_xyz.val[1], world_map_center_y_4);float32x4_t mul_2 = vmulq_f32(sub_2, mulFactor);float32x4_t add_2 = vaddq_f32(mul_2, vreinterpretq_f32_s32(pixel_map_center_y_4));int32x4_t col_4 = vcvtq_s32_f32(add_2);// 存回输出数组int32_t output_x[4];int32_t output_y[4];vst1q_s32(output_x, row_4);vst1q_s32(output_y, col_4);
}
3.2.3 效果对比
运行时间对比
[I][map_base_function.cpp|568] contour_MowerMapContour_to_cvPoints Input(size=960)
[I][map_base_function.cpp|647] neno function took 0.000006 seconds to execute.
[I][map_base_function.cpp|677] c function took 0.000012 seconds to execute.
三、注意事项
数据转换的输入坐标点个数需要限制是4的倍数,这样才能确保处理的数据和Neon寄存器对齐。即一个q寄存器存入4个float,做并行操作。
附录
rgb_neon.cpp
#include "MapNode.hpp"
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include "opencv2/opencv.hpp"
#include <time.h>// C version void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {/** Take the elements of "rgb" and store the individual colors "r", "g", and "b".*/for (int i=0; i < len_color; i) {r[i] = rgb[3*i];g[i] = rgb[3*i1];b[i] = rgb[3*i2];}}// NEON version void rgb_deinterleave_neon(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {/** Take the elements of "rgb" and store the individual colors "r", "g", and "b"*/int num8x16 = len_color / 16;uint8x16x3_t intlv_rgb;for (int i=0; i < num8x16; i) {intlv_rgb = vld3q_u8(rgb3*16*i);vst1q_u8(r16*i, intlv_rgb.val[0]);vst1q_u8(g16*i, intlv_rgb.val[1]);vst1q_u8(b16*i, intlv_rgb.val[2]);}}int main(int argc, char **argv){LOG_INIT("map",LOG_DEVICE_7); //只需要初始化调用一次DBG_LOG("map start!!!\n");std::cout << "map start!!!" << std::endl;{int len_color = 1920*1080;uint8_t *rgb = (uint8_t*)malloc(len_color*3*sizeof(uint8_t));uint8_t *r = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *g = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *b = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *r2 = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *g2 = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *b2 = (uint8_t*)malloc(len_color*sizeof(uint8_t));// fill the rgb array with some dummy valuesfor (int i=0; i < len_color*3; i) {rgb[i] = i%255;}// deinterleave the rgb array into the r, g, and b arraysclock_t start, end;double cpu_time_used;/*for (int i = 0; i < 20; i) {DBG_LOG("r[%d] = %u, g[%d] = %u, b[%d] = %u\n", i, r[i], i, g[i], i, b[i]);}*/// 计时器开始start = clock();// 执行函数rgb_deinterleave_c(r, g, b, rgb, len_color);// 计时器结束end = clock();cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;DBG_LOG("rgb_deinterleave_c function took %f seconds to execute.\n", cpu_time_used);free(r);free(g);free(b);/*for (int i = 0; i < 20; i) {DBG_LOG("r[%d] = %u, g[%d] = %u, b[%d] = %u\n", i, r[i], i, g[i], i, b[i]);}*//*for (int i = 0; i < 20; i) {DBG_LOG("r2[%d] = %u, g2[%d] = %u, b2[%d] = %u\n", i, r2[i], i, g2[i], i, b2[i]);}*/start = clock();rgb_deinterleave_neon(r2, g2, b2, rgb, len_color);end = clock();cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;DBG_LOG("rgb_deinterleave_neon function took %f seconds to execute.\n", cpu_time_used);/*for (int i = 0; i < 20; i) {DBG_LOG("r2[%d] = %u, g2[%d] = %u, b2[%d] = %u\n", i, r2[i], i, g2[i], i, b2[i]);}*/// 释放内存free(rgb);free(r2);free(g2);free(b2);}// 初始化ROS节点ros::init(argc, argv, "eros_node_map");// 分离式线程// m_hardWareThread.detach();ros::spin();return 0;}
map_neon.cpp
#include "MapNode.hpp"
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include "opencv2/opencv.hpp"
#include <time.h>// C version void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {/** Take the elements of "rgb" and store the individual colors "r", "g", and "b".*/for (int i=0; i < len_color; i) {r[i] = rgb[3*i];g[i] = rgb[3*i1];b[i] = rgb[3*i2];}}// NEON version void rgb_deinterleave_neon(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {/** Take the elements of "rgb" and store the individual colors "r", "g", and "b"*/int num8x16 = len_color / 16;uint8x16x3_t intlv_rgb;for (int i=0; i < num8x16; i) {intlv_rgb = vld3q_u8(rgb3*16*i);vst1q_u8(r16*i, intlv_rgb.val[0]);vst1q_u8(g16*i, intlv_rgb.val[1]);vst1q_u8(b16*i, intlv_rgb.val[2]);}}int main(int argc, char **argv){LOG_INIT("map",LOG_DEVICE_7); //只需要初始化调用一次DBG_LOG("map start!!!\n");std::cout << "map start!!!" << std::endl;{int len_color = 1920*1080;uint8_t *rgb = (uint8_t*)malloc(len_color*3*sizeof(uint8_t));uint8_t *r = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *g = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *b = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *r2 = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *g2 = (uint8_t*)malloc(len_color*sizeof(uint8_t));uint8_t *b2 = (uint8_t*)malloc(len_color*sizeof(uint8_t));// fill the rgb array with some dummy valuesfor (int i=0; i < len_color*3; i) {rgb[i] = i%255;}// deinterleave the rgb array into the r, g, and b arraysclock_t start, end;double cpu_time_used;/*for (int i = 0; i < 20; i) {DBG_LOG("r[%d] = %u, g[%d] = %u, b[%d] = %u\n", i, r[i], i, g[i], i, b[i]);}*/// 计时器开始start = clock();// 执行函数rgb_deinterleave_c(r, g, b, rgb, len_color);// 计时器结束end = clock();cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;DBG_LOG("rgb_deinterleave_c function took %f seconds to execute.\n", cpu_time_used);free(r);free(g);free(b);/*for (int i = 0; i < 20; i) {DBG_LOG("r[%d] = %u, g[%d] = %u, b[%d] = %u\n", i, r[i], i, g[i], i, b[i]);}*//*for (int i = 0; i < 20; i) {DBG_LOG("r2[%d] = %u, g2[%d] = %u, b2[%d] = %u\n", i, r2[i], i, g2[i], i, b2[i]);}*/start = clock();rgb_deinterleave_neon(r2, g2, b2, rgb, len_color);end = clock();cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;DBG_LOG("rgb_deinterleave_neon function took %f seconds to execute.\n", cpu_time_used);/*for (int i = 0; i < 20; i) {DBG_LOG("r2[%d] = %u, g2[%d] = %u, b2[%d] = %u\n", i, r2[i], i, g2[i], i, b2[i]);}*/// 释放内存free(rgb);free(r2);free(g2);free(b2);}// 初始化ROS节点ros::init(argc, argv, "eros_node_map");// 分离式线程// m_hardWareThread.detach();ros::spin();return 0;}
这篇关于NEON 内嵌函数梳理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!