本文旨在介绍ARMv7开始增加的一项advanced SIMD extension——NEON技术。有助于帮助读者理解NEON概况,提供的实例分析有助于迅速上手NEON编程。阅读此文要求读者有基本的C/C++经验及汇编代码经验,若没有也没关系,多理解查阅资料即可。Good luck~!
目录
SIMD及NEON概览
SIMD
NEON
NEON架构(数据类型/寄存器/指令集)
NEON支持的数据类型
NEON寄存器(重点)
NEON指令集(重点)
SIMD及NEON概览
SIMD
Single Instruction Multiple Data (SIMD)顾名思义就是“一条指令处理多个数据(一般是以2为底的指数数量)”的并行处理技术,相比于“一条指令处理几个数据”,运算速度将会大大提高。它是Michael J. Flynn在1966年定义的四种计算机架构之一(根据指令数与数据流的关系定义,其余还有SISD、MISD、MIMD)。
许多程序需要处理大量的数据集,而且很多都是由少于32bits的位数来存储的。比如在视频、图形、图像处理中的8-bit像素数据;音频编码中的16-bit采样数据等。在诸如上述的情形中,很可能充斥着大量简单而重复的运算,且少有控制代码的出现。因此,SIMD就擅长为这类程序提供更高的性能,比如下面几类:
- 块数据处理(Block-based data processing)
- 音视频和图像处理(Audio, video, and image processing codes)
- 基于矩形像素块的 2D 图形(2D graphics based on rectangular blocks of pixels).
- 3D图形(3D graphics).
- 色彩空间转换(Color-space conversion).
- 物理模拟(Physics simulations).
在32-bit内核的处理器上,如Cortex-A系列,如果不采用SIMD则会将大量时间花费在处理8-bit或16-bit的数据上,但是处理器本身的ALU、寄存器、数据深度又是主要为了32-bit的运算而设计的。因此NEON应运而生。
NEON
NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bit宽的向量运算(vector operations)。NEON技术从ARMv7开始被采用,目前可以在ARM Cortex-A和Cortex-R系列处理器中采用。
NEON在Cortex-A7、Cortex-A12、Cortex-A15处理器中被设置为默认选项,但是在其余的ARMv7 Cortex-A系列中是可选项。NEON与VFP共享了同样的寄存器,但它具有自己独立的执行流水线。
NEON架构(数据类型/寄存器/指令集)
NEON支持的数据类型
- 32-bit单精度浮点数(32-bit single precision floating-point);
- 8, 16, 32 and 64-bit无符号/有符号整型(8, 16, 32 and 64-bit unsigned and signed integers);
- 8 and 16-bit多项式(8 and 16-bit polynomials )。
NEON数据类型说明符:
- 无符号整型: Unsigned integer U8 U16 U32 U64
- 有符号整型: Signed integer S8 S16 S32 S64
- 未指定类型的整数: Integer of unspecified type I8 I16 I32 I64
- 浮点数: Floating-point number F16 F32
- {0,1} 上的多项式: Polynomial over {0,1} P8
注:F16不适用于数据处理运算,只用于数据转换,仅用于实现半精度体系结构扩展的系统。
多项式算术在实现某些加密、数据完整性算法中非常有用。
NEON寄存器(重点)
NEON寄存器有几种形式:
- 16×128-bit寄存器(Q0-Q15);
- 或32×64-bit寄存器(D0-D31)
- 或上述寄存器的组合。
注:每一个Q0-Q15寄存器映射到一对D寄存器。
寄存器之间的映射关系:
- D<2n> 映射到 Q 的最低有效半部;
- D<2n+1> 映射到 Q 的最高有效半部;
结合NEON支持的数据类型,NEON寄存器有如下图的几种形态:
NEON 数据处理指令可分为:
- 普通指令 对任何向量类型进行操作,并产生与操作数向量相同大小且通常相同类型的结果向量。
- 长指令 操作双字向量,生成四倍长字向量。 结果的宽度一般比操作数加倍,同类型。 在指令中加L。
- 宽指令 操作双字 + 四倍长字,生成四倍长字。 结果和第一个操作数都是第二个操作数的两倍宽度。 在指令中加W。
- 窄指令 操作四倍长字,生成双字。 结果宽度一般是操作数的一半。在指令中加N。
- 饱和变体(Saturating variants)
- ARM中的饱和算法:
- 对于有符号饱和运算,如果结果小于 –2^n,则返回的结果将为 –2^n;
- 对于无符号饱和运算,如果整个结果将是负值,那么返回的结果是 0;如果结果大于 2^n – 1,则返回的结果将为 2^n – 1;
- NEON中的饱和算法:通过在V和指令助记符之间使用Q前缀可以指定饱和指令,原理与上述内容相同。
下面给出几幅图解释上述指令的操作原理,图片来自Search Results Cortex-A Series Programmer’s Guide
NEON指令集(重点)
ARMv7/AArch32指令格式
所有的支持NEON指令都有一个助记符V
,下面以32位指令为例,说明指令的一般格式:
V{<mod>}<op>{<shape>}{<cond>}{.<dt>}{<dest>}, src1, src2
- <mod>
- Q: 该指令使用饱和算法,使结果在指定数据类型范围内饱和,例如 VQABS、VQSHL 等。.
- H: 该指令将结果减半。 它通过向右移动一个位置(实际上是被截断的二分之一)来实现,例如 VHADD、VHSUB。
- D: 该指令将结果加倍,例如 VQDMULL、VQDMLAL、VQDMLSL 和 VQ{R}DMULH。
- R: 指令会对结果进行四舍五入,相当于在截断前给结果加0.5,例如VRHADD、VRSHR。
- <op> - 操作码(例如,ADD、SUB、MUL)。.
- <shape> - Shape,即前文中的Long (L), Wide (W), Narrow (N).
- <cond> - 条件(Condition),与 IT 指令一起使用.
- <.dt> - 数据类型,如s8、u8、f32等.
- <dest> - 目标数.
- <src1> - 源数据1
- <src2> - 源数据2.
注: {} 表示可选的参数。
比如:
VADD.I16 D0, D1, D2 @ 16位加法
VMLAL.S16 Q2, D8, D9 @ 有符号16位乘加
NEON支持的指令总结
- 运算:和、差、积、商
- 共享的 NEON 和 VFP 指令:涉及加载、多寄存器间的传送、存储
注:VFP指令与NEON可能相像,助记符也可能与NEON指令相同,但是操作数等等是不同的,涉及多个基本运算。