转自 http://blog.sina.com.cn/s/blog_602f87700102wj5w.html
作者:Sam (甄峰) [email protected]
其他优秀链接:https://blog.csdn.net/hemmingway/article/details/44832013
之前在一些ARM CPU下,曾在编译时指定过Neon。
0. Neon简介:
0.1: 简介:
ARM Advanced SIMD延伸集,(ARM Cortex-A系列处理器的128位SIMD架构扩展)称为NEON技术,它是一个结合64bit和128bit的SIMD(Single Instruction Multiple Data单指令多数据)指令集。其针对多媒体和讯号处理程式具备标准化加速的能力,NEON具有一组广泛的指令集、各自的寄存器阵列,以及独立执行的硬件。ARM NEON技术可加速多媒体和信号处理算法(如视频编码/解码、2D/3D图形、游戏、音频和语音处理、图像处理技术、电话和声音合成)。
NEON的寄存器:有16个128位四字寄存器Q0-Q15,32个64位双字寄存器D0-D31,两个寄存器是重叠的,在使用的时候需要特别注意,不小心就会被覆盖掉。
0.2: SIMD:
通常我们进行多媒体处理的时候,很多的数据都是16位或者8位的,如果这些程序运行在32位的机器上,那么计算机有一部分的计算单元是没有工作的.所以这是一种浪费.为了更好的使用那些被浪费的资源.SIMD就应运而生了.SIMD这种技术就是使用一条指令,但对多个相同类型和尺寸的数据进行并行处理.就像我们现实生活中的好几个人都在做同一件事情那样,这样就可以将速度提升很多倍
0.3:使用Neon的方式:
按Sam的理解,使用Neon的方式有以下几种:
A:使用C的neon内联函数。
B:直接使用Neon汇编指令。
C:使用某些第三方库如OpenMAX.
各自的优缺点:
A:使用Neon intrinsics函数,可以在直接接触ASM的情况下,使用Neon。这些函数被定义在:
arm_neon.h 中。
类似于:
vadd_s8 (int8x8_t __a, int8x8_t __b)
此方法需要注意2点:
1.必须: #include
2.编译时必须加入; -mfloat-abi=softfp -mfpu=neon
B:使用汇编指令:
效率最高. 使用intrinsics没法控制寄存器分配和内存对齐等。
1.SIMD寄存器基本知识:
SIMD寄存器共有16个128位的向量寄存器构成。而32个双精度浮点寄存器共享了这16个寄存器;32个单精度浮点寄存器共享了前8个寄存器。因此,这寄存器组是与浮点寄存器共享的.
单精度寄存器用s0~s31表示;(32bit)
双精度寄存器用d0~d31来表示;(64bit)
而128位的SIMD寄存器则用q0~q15来表示.(128bit)
2. Neon的数据从内存向寄存器加载(vld)和从寄存器向内存存储(vst):
如何灵活有效的加载和存储数据,对SIMD来说非常重要。先举一下例子:
24-bit的RGB图像,像素在内存里的组织方式是R, G, B, R, G, B...,如果你想做一个简单的图像处理,比如把R和B通道互换,你该如何高效的使用NEON协处理器呢?
首先想到的办法是:从存储空间线性加载RGB数据到D寄存器(64位双精度寄存器),然后交换R和B数据。 但是这种线性加载的数据进行R和B通道的数据交换非常麻烦,需要首先掩码mask,然后移位并合并掩码数据。这种复杂的运算显然并不高效。如图所示:
从r0内存处,开始读取数据到寄存器。
D0,D1,D2这三个64bit寄存器中各放置了杂乱的8个色数据。这样非常不利于快速计算。
NEON提供了各种结构的加载和存储指令来处理这种情况,这些指令能把数据从存储区加载的同时还能把这些值分开存储到不同的寄存器中。
从r0内存处开始读取数据。
然后使用VLD3分开加载的数据就能方便的使用指令(VSWP d0, d2)来进行R和B通道的交换了。
然后把结果再写入内存,当然也要使用interleave交织模式的存储,即VST3存储指令。
3. 结构化加载和存储语法和具体指令:
NEON结构化加载会读取内存内容到64-bit的NEON寄存器,使用可选的deinterleave选项,同样加载指令也可以采用这种reinterleave的方式把寄存器的内容写到内存空间。
NEON存储和加载的结构化方式,语法包括如下5个部分:
交织模式:Interleave Pattern:
加载和存储指令可以用从1到4个相同大小的元素的交织结构体,这些元素可以是NEON指令通常支持的8,16或者32比特。
存储和加载类似,只是把寄存器的数据interleave然后写到内存。
元素类型 Element Types
加载和存储interleave的数据的基本元素可以为8,16或者32比特的数据。比如NEON指令VLD2.16 {d0, d1}将加载4个16-bit元素到第一个寄存器,然后4个16-bit元素到第二个寄存器,把临近的奇偶对分开保存到每个寄存器。
把元素大小变成32-bits还是加载相同大小的数据,但是只有2个元素来构成一个向量,同样分成奇偶元素部分。
4. 关于优化的几个例子:
// C version
void add_int_c(int* dst, int* src1, int* src2, int count)
{
int i;
for (i = 0; i < count; i++)
{
dst[i] = src1[i] + src2[i];
}
}
// NEON version
void add_float_neon1(int* dst, int* src1, int* src2, int count)
{
int i;
for (i = 0; i < count; i += 4)
{
int32x4_t in1, in2, out;
in1 = vld1q_s32(src1);
src1 += 4;
in2 = vld1q_s32(src2);
src2 += 4;
out = vaddq_s32(in1, in2);
vst1q_s32(dst, out);
dst += 4;
}
}
其中 vld1q_s32(src1):
被解析为: vld1.32 {d0, d1}, [r0]
vld: load数据到寄存器。 1:表示依次读取。32:每份数据32bit. d0, d1,两个64bit寄存器。所以,表示要读4份数据。 r0:从r0内存处开始读。