一、NEON简介
ARM NEON技术是基于SIMD的理念而设计出的,它是一种64位和128位混合的SIMD技术,主要应用场景是音视频处理,图像视觉计算,信号处理应用等需要密集计算的场景。ARMv7架构的处理器中A系列和R系列基本都带有NEON扩展,并且向后兼容。
对NEON的更多了解可产考官网:
Neon (arm.com)https://developer.arm.com/Architectures/Neon
ARM提供了四种方式调用NEON技术实现代码优化,分别是NEON指令集、NEON汇编、NEON开源库和编译器自动向量化。接下来将对NEON指令集的使用做一个简单的介绍。
二、NEON指令的基本用法
1、引入头文件
如要使用NEON指令集,则需要引入头文件:
#include<arm_neon.h>
引入了头文件之后,就可以使用其中的intrinsic函数来执行向量化指令
2、intrinsic函数的使用
NEON可以通过调用intrinsic函数来实现向量化,其基本的逻辑为:
①将基本数据类型(如int、float)加载到向量中;
②调用指令函数对向量进行运算并用一个结果向量存储;
③从结果向量中读取计算的结果。
可以看到无非是“放数据——计算——取数据”三个阶段。
3、一些基本的intrinsic函数
对于详细的intrinsic函数,可以参见官网:
Intrinsics – Arm Developerhttps://developer.arm.com/architectures/instruction-sets/intrinsics/
以下作一个基本的介绍
(1)数据类型
NEON中的数据类型指的就是各种形式的向量,它们一般遵循以下这种命名方式:
<type><size>x<lanes>_t
①type:数据类型,如int,uint,float
②size:数据大小,如8位、16位、32位、64位
③lanes:通道数,由于NEON提供了64位和128位寄存器,所以size * lanes 应为64或128
以128位寄存器为例,数据类型如:int8x16_t,int16x8_t,int32x4_t,int64x2_t,uint8x16_t,uint168_t,uint32x4_t,uint64x2_t,float16x8_t,float32x4_t
(2)指令函数
指令函数一般遵循这种命名:
v<mod><operate><shape><flags>_<type>
①mod:计算模式,如q表示饱和运算,即当计算结果溢出时,结果取类型范围内的最大值或最小值
②operate:计算操作,如加法:add;减法:sub;乘法:mul;加载数据:ld;读取数据:st
③shape:计算形式,如l,表示long,可以使返回的结果向量元素是操作数元素的两倍
④flags:寄存器长度,若存在q时,表示使用128位的寄存器,否则使用64位寄存器
⑤types:单个通道的数据类型,如u8,u16,s8,s16(s表示int)
按照上述的规则,可以举一些例子:
vadd_s16:两个int16x4的向量相加,并返回一个int16x4的结果向量
vqsubq_s8:两个int8x16的向量做饱和相加,并返回一个int8x16的结果向量
vld1q_s16:将数据存储到int8x16的寄存器里
三、实例
一个数组求和函数:
void sum(float *a, float *b, float *c, int n)
{
for(int i=0;i<n;i++)
c[i] += a[i] + b[i];
}
用NEON向量化之后:
void vsum(float *a, float *b, float *c, int n)
{
float32x4 vc, va, vb;
for(int i=0;i<n;i+=4){
va = vld1q_f32(&a[i]);
vb = vld1q_f32(&b[i]);
vc = vaddq_f32(va, vb);
vst1q_f32(&c[i], vc);
}
}