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

相关文章

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C

Java8需要知道的4个函数式接口简单教程

《Java8需要知道的4个函数式接口简单教程》:本文主要介绍Java8中引入的函数式接口,包括Consumer、Supplier、Predicate和Function,以及它们的用法和特点,文中... 目录什么是函数是接口?Consumer接口定义核心特点注意事项常见用法1.基本用法2.结合andThen链

MySQL 日期时间格式化函数 DATE_FORMAT() 的使用示例详解

《MySQL日期时间格式化函数DATE_FORMAT()的使用示例详解》`DATE_FORMAT()`是MySQL中用于格式化日期时间的函数,本文详细介绍了其语法、格式化字符串的含义以及常见日期... 目录一、DATE_FORMAT()语法二、格式化字符串详解三、常见日期时间格式组合四、业务场景五、总结一、

golang panic 函数用法示例详解

《golangpanic函数用法示例详解》在Go语言中,panic用于触发不可恢复的错误,终止函数执行并逐层向上触发defer,最终若未被recover捕获,程序会崩溃,recover用于在def... 目录1. panic 的作用2. 基本用法3. recover 的使用规则4. 错误处理建议5. 常见错