NEON简单总结

博客对NEON加速做了简单总结,给出参考例子。使用NEON需引入头文件<arm_neon.h>,可按其编程指令进行程序加速优化,还介绍了NEON函数命名规则、编译指令,以及常用的CPU类型编译器选项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

对NEON加速做一个简单的总结,遇到其他问题再记录整理,这里感谢几位博主的奉献。

先贴一个简单的例子,例子参考https://blog.youkuaiyun.com/dwyane12138/article/details/78697210。

#include<iostream>
#include<time.h>
#include <arm_neon.h>

using namespace std;

float sum_array(float* arr,int len);
float sum_array_neon(float* arr,int len);

double time_calc(struct timespec &tp_start, struct timespec &tp_end)
{
  	return (double)(tp_end.tv_nsec -tp_start.tv_nsec) * 0.000001 + (double)(tp_end.tv_sec - tp_start.tv_sec) * 1000.0;
}

int main()
{ 
	long l=100000;
  	float array[l];
  	for(long i=0;i<l;i++)
    {
      	array[i]=1.0/3.0;//i/10000;
    } 

	struct timespec tp0,tp1,tp2,tp3;
	float Sum,Sum1;
	
	//calcuate without NEON
  	clock_gettime(CLOCK_MONOTONIC, &tp0);
 	//for(int round=1;round<=1000;round++)
 	//{ 
  		Sum=sum_array(array,l);
  		//cout<<Sum<<endl;
 	//}
 	clock_gettime(CLOCK_MONOTONIC, &tp1);
	printf ("no neon result = %f\n",Sum);
	printf ("no neon time=%lf ms \n\n\n", time_calc(tp0, tp1));
	
	//calculate with NEON
  	clock_gettime(CLOCK_MONOTONIC, &tp2);
 	//for(int round=1;round<=1000;round++)
 	//{ 
  		Sum1=sum_array_neon(array,l);
  		//cout<<Sum<<endl;
 	//}
 	clock_gettime(CLOCK_MONOTONIC, &tp3);
	printf ("neon result = %f\n",Sum1);
	printf ("with neon time=%lf ms \n", time_calc(tp2, tp3));
	
  	return 0;
}
 
float sum_array(float* arr,int len)
{ 
  	if(NULL== arr||len<1)
  	{
    	cout<<"input error\n";
     	return 0;
  	}
  	float sum(0.0);
  	for(long i=0;i<len;++i)
  	{
    	sum+=*arr++;
  	}
  
  	return sum;
}

float sum_array_neon(float* arr,int len)
{ 
	if(NULL== arr||len<1)
  	{
    	cout<<"input error\n";
      	return 0;
  	}
  	long dim4=len/4;
  	int left4=len%4;
  	float32x4_t sum_vec = vdupq_n_f32(0.0);
  	for(;dim4>0;dim4--,arr+=4)
    {
      	float32x4_t data_vec = vld1q_f32(arr);
      	sum_vec = vaddq_f32(sum_vec,data_vec);
    }
  	float sum = vgetq_lane_f32(sum_vec,0)+vgetq_lane_f32(sum_vec,1)+vgetq_lane_f32(sum_vec,2)+vgetq_lane_f32(sum_vec,3);
  	for(;left4>0;left4--,arr++)
    {
     	sum+=(*arr);
    }
   
  	return sum;
}

      使用NEON首先引入头文件<arm_neon.h>,其次就可以根据NEON的编程指令进行程序的加速和优化,下面将上面用到的函数介绍如下,此处借用无眠栀的介绍:

上面用到的函数有:
float32x4_t vdupq_n_f32 (float32_t value)
将value复制4分存到返回的寄存器中

float32x4_t vld1q_f32 (float32_t const * ptr)
从数组中依次Load4个元素存到寄存器中

相应的 有void vst1q_f32 (float32_t * ptr, float32x4_t val)
将寄存器中的值写入数组中

float32x4_t vaddq_f32 (float32x4_t a, float32x4_t b)
返回两个寄存器对应元素之和 r = a+b

相应的 有float32x4_t vsubq_f32 (float32x4_t a, float32x4_t b)
返回两个寄存器对应元素之差 r = a-b

float32_t vgetq_lane_f32 (float32x4_t v, const int lane)
返回寄存器某一lane的值

其他常用的函数还有:

float32x4_t vmulq_f32 (float32x4_t a, float32x4_t b)
返回两个寄存器对应元素之积 r = a*b

float32x4_t vmlaq_f32 (float32x4_t a, float32x4_t b, float32x4_t c)
r = a +b*c

float32x4_t vextq_f32 (float32x4_t a, float32x4_t b, const int n)
拼接两个寄存器并返回从第n位开始的大小为4的寄存器 0<=n<=3
例如
a: 1 2 3 4
b: 5 6 7 8
vextq_f32(a,b,1) -> r: 2 3 4 5
vextq_f32(a,b,2) -> r: 3 4 5 6
vextq_f32(a,b,3) -> r: 4 5 6 7

关于NEON函数的命名规则介绍如下,参考stn_lcd

数据类型  

NEON Intrinsics内置的整数数据类型主要包括以下几种:

    (u)int8x8_t;
    (u)int8x16_t;
    (u)int16x4_t;
    (u)int16x8_t;
    (u)int32x2_t;
    (u)int32x4_t;
    (u)int64x1_t;

其中,第一个数字代表的是数据类型宽度为8/16/32/64位,第二个数字代表的是一个寄存器中该类型数据的数量。如int16x8_t代表16位有符号数,寄存器中共有8个数据。
常用指令

NEON Intrinsics支持的所有指令可参看ARM NEON Intrinsics,其包含了常用的arm汇编指令类型,如数学运算,逻辑运算等。另外,其引入了有针对性的加载/存储/转置/交叉存取等指令。部分常见的指令在会下面的示例环节中予以说明。需要注意的是,指令中的助记符与arm汇编是相同的。
示例1:

    int16x8_t vqaddq_s16 (int16x8_t, int16x8_t)
    int16x4_t vqadd_s16 (int16x4_t, int16x4_t)
    第一个字母'v'指明是vector向量指令,也就是NEON指令;
    第二个字母'q'指明是饱和指令,即后续的加法结果会自动饱和;
    第三个字段'add'指明是加法指令;
    第四个字段'q'指明操作寄存器宽度,为'q'时操作QWORD, 为128位;未指明时操作寄存器为DWORD,为64位;
    第五个字段's16'指明操作的基本单元为有符号16位整数,其最大表示范围为-32768 ~ 32767;
    形参和返回值类型约定与C语言一致。

其它可能用到的助记符包括:

    l 长指令,数据扩展
    w 宽指令,数据对齐
    n 窄指令, 数据压缩

示例2

    uint8x8_t vld1_u8 (const uint8_t *)
    第二个字段'ld'表示加载指令
    第三个字段'1'(注意是1,不是l)表示顺次加载。如果需要处理图像的RGB分量,可能会用到vld3。关于vld/vst指令更详细的说明,请自己参阅arm官方文档。

编译指令介绍如下,此处引用贺二公子的总结:

常用的CPU类型编译器选项

CPU类型CPU类型选项FP选项FP + SIMD选项备注
Cortex-A5-mcpu=cortex-a5-mfpu=vfpv3-fp16
-mfpu=vfpv3-d16-fp16
-mfpu=neon-fp16-d16表明只有前16个浮点寄存器可用
Cortex-A7-mcpu=cortex-a7-mfpu=vfpv4
-mfpu=vfpv4-d16
-mfpu=neon-vfpv4-fp16表明支持16bit半精度浮点操作
Cortex-A8-mcpu=cortex-a8-mfpu=vfpv3-mfpu=neon 
Cortex-A9-mcpu=cortex-a9-mfpu=vfpv3-fp16
-mfpu=vfpv3-d16-fp16
-mfpu=neon-fp16 
Cortex-A15-mcpu=cortex-a15-mfpu=vfpv4-mfpu=neon-vfpv4 

 


 

### 关于ARM NEON指令集 NEON 是一种 SIMD (Single Instruction Multiple Data) 指令集,专为 ARM 架构设计。它能够显著提升多媒体信号处理应用程序的性能,例如图像处理、音频解码以及视频编码等场景[^1]。 #### NEON 的基本概念 NEON 提供了一组专门用于并行计算的寄存器指令集合。这些寄存器通常被划分为多个 64 位或 128 位宽的数据单元,允许一次加载多个数据项并对它们同时执行相同的运算。这种机制被称为向量化(vectorization)[^5]。 #### 编程模型 为了充分利用 NEON 的功能,在开发过程中可以通过以下几种方式实现: - **汇编语言**:直接编写 NEON 汇编代码来控制硬件行为。 - **C/C++ 内联汇编**:通过 GCC 或 Clang 支持的扩展语法嵌入 NEON 指令到高级语言中。 - **intrinsics 函数**:这是最常用的方法之一,利用编译器提供的内在函数接口调用底层 NEON 功能而无需手动写汇编代码[^4]。 #### 示例代码展示 以下是基于 intrinsics 方法的一个简单例子,演示如何使用 NEON 加速两个浮点数组相加的过程: ```c #include <arm_neon.h> #include <stdio.h> void neon_add(float *a, float *b, float *result, int n) { int i; for(i=0;i<n/4*4;i+=4){ // 将四个连续的float装载进一个128bit NEON register里 float32x4_t va = vld1q_f32(&a[i]); float32x4_t vb = vld1q_f32(&b[i]); // 执行逐元素加法 float32x4_t vc = vaddq_f32(va,vb); // 存储回内存 vst1q_f32(&result[i],vc); } } int main(){ const int N = 16; // 假设长度能整除4以便简化循环逻辑 float a[N]={1.,2.,...}; // 初始化输入数组A float b[N]={3.,4.,...}; // 初始化输入数组B float result[N]; neon_add(a,b,result,N); printf("Result:\n"); for(int j=0;j<N;++j){printf("%f ",result[j]);} return 0; } ``` 此段代码展示了如何定义 `neon_add` 函数来进行批量浮点数加法操作,并且包含了必要的头文件 `<arm_neon.h>` 来访问 NEON intrinsic 函数[^2]。 ### 总结 上述内容涵盖了 NEON 指令的基础理论及其实际应用方法。对于希望提高其算法效率特别是涉及大量数值计算的应用来说,学习掌握 NEON 技术是非常有价值的[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值