NEON 内嵌函数梳理

2024-03-30 12:04
文章标签 函数 内嵌 梳理 neon

本文主要是介绍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:数据类型,intuintfloat
  • size:元素数据长度,整型长度为8163264浮点型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_u8ld 类似表示从内存加载 load到寄存器,此处,按移位的方法将rgb数据赋值给变量intlv_rgb;
  • vst1q_u8(r+16*i, intlv_rgb.val[0]): 注意 vst1q_u8st 类似表示从寄存器写入内存,此处,将 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 内嵌函数梳理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/861443

相关文章

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C++11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆,该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使用了由[90]描述的第一个算法。开发者应该注意,由于数据点靠近包含的 Mat 元素的边界,返回的椭圆/旋转矩形数据

Unity3D 运动之Move函数和translate

CharacterController.Move 移动 function Move (motion : Vector3) : CollisionFlags Description描述 A more complex move function taking absolute movement deltas. 一个更加复杂的运动函数,每次都绝对运动。 Attempts to

梳理2024年,螺丝钉们爱用的3款剪辑软件

这年头,视频到处都是,就跟天上的星星一样数不清。不管你是公司里的新面孔,还是职场上的老狐狸,学会怎么剪视频,就好比找到了赢的秘诀。不管是给上司汇报工作,展示你的产品,还是自己搞点小视频记录生活,只要是剪辑得漂亮,肯定能一下子吸引大家的目光,让人记得你。咱们今天就来侃侃现在超火的三款视频剪辑工具,尤其是PR剪辑,你肯定听说过,这货在剪辑界可是大名鼎鼎,用它剪视频,既专业又麻利。 NO1. 福昕轻松

✨机器学习笔记(二)—— 线性回归、代价函数、梯度下降

1️⃣线性回归(linear regression) f w , b ( x ) = w x + b f_{w,b}(x) = wx + b fw,b​(x)=wx+b 🎈A linear regression model predicting house prices: 如图是机器学习通过监督学习运用线性回归模型来预测房价的例子,当房屋大小为1250 f e e t 2 feet^

JavaSE(十三)——函数式编程(Lambda表达式、方法引用、Stream流)

函数式编程 函数式编程 是 Java 8 引入的一个重要特性,它允许开发者以函数作为一等公民(first-class citizens)的方式编程,即函数可以作为参数传递给其他函数,也可以作为返回值。 这极大地提高了代码的可读性、可维护性和复用性。函数式编程的核心概念包括高阶函数、Lambda 表达式、函数式接口、流(Streams)和 Optional 类等。 函数式编程的核心是Lambda

PHP APC缓存函数使用教程

APC,全称是Alternative PHP Cache,官方翻译叫”可选PHP缓存”。它为我们提供了缓存和优化PHP的中间代码的框架。 APC的缓存分两部分:系统缓存和用户数据缓存。(Linux APC扩展安装) 系统缓存 它是指APC把PHP文件源码的编译结果缓存起来,然后在每次调用时先对比时间标记。如果未过期,则使用缓存的中间代码运行。默认缓存 3600s(一小时)。但是这样仍会浪费大量C