翻译可能有偏差,描述可能有错误,请以原著为准
本书为码农们提供了一份指导,以高效地使用NEON技术,NEON是ARM的先进SIMD(Single Instruction Multiple Data)架构扩展。本书提供的信息对汇编语言和C语言都是很有用的。
本书介绍了NEON技术,该技术已在实现ARMv7–A或ARMv7–R体系结构配置文件的ARM Cortex™-A系列处理器上使用。
单指令单操作
每个操作指定要处理的单个数据,处理多个数据需要多条指令。下面的代码用4条指令把8个寄存器相加:
add r0, r5 @ 意思是r0 = r0 + r5
add r1, r6
add r2, r7
add r3, r8
这种方式很慢,并且很难看到不同寄存器之间是怎么关联的。为了提高效率和性能,媒体处理通常下放到不同的处理器,例如图形处理单元(Graphics Processing Unit、GPU)或者媒体处理单元(Media Processing Unit),这些单元可以通过单指令处理多个数据。
在一个32位ARM处理器上,处理大量的8位或者16位单独操作很难有效地利用机器资源,因为处理器、寄存器和数据路径都是为了32位的计算而设计的。
SMID单指令多数据(向量模式)
一个操作可以指定对多个数据源进行相同的处理。 如果控制寄存器中的LEN
值为4,则单个向量加法指令将执行四个加法:
VADD.F32 S24, S8, S16
// 以上操作产生4条加法:
// S24 = S8 +S16
// S25 = S9 +S17
// S26 = S10 +S18
// S27 = S11 +S20
尽管只有一条指令,但是它在4个步骤中按顺序执行了4次加法。
在ARM的术语中,这种叫做Vector Floating Point(VPF 向量浮点)。
向量浮点(VFP)扩展是在ARMv5架构中引入的,并执行了短向量指令以加快浮点操作。 源寄存器和目标寄存器可以是用于标量运算的单个寄存器,也可以是用于向量运算的两个至八个寄存器的序列。
由于SIMD操作比VFP操作更有效地执行向量计算(vector calculation),因此从ARMv7引入以来,向量模式操作已被弃用,并被NEON技术所取代,该技术对宽寄存器执行多种操作。
浮点(floating-point)和NEON使用公共寄存器组进行操作。
SIMD单指令多数据(打包的数据模式)
一个操作可以指定对存储在一个大寄存器中的多个数据字段进行相同的处理:
VADD.I16 Q10, Q8, Q9
// 这个操作把两个64位寄存器相加
// 但是把寄存器中每16位作为一个lane,寄存器中4个lane被分开单独相加
// 元素之间互不影响
就像图1那样,这一条指令对一个大寄存器的所有数据同时处理,这种方法更快。
lane0
中)的溢出或者进位不会影响到第16位(在lane1
中),因为每个lane
是分隔开计算的lane0
~lane3
,NEON寄存器还支持其它的组合:
移动设备中使用的媒体处理器,通常将每个完整的数据寄存器分成多个子寄存器,并并行地对子寄存器执行计算。 如果对数据集的处理简单且重复很多次,则SIMD可以显着提高性能。 对于数字信号处理或多媒体算法,例如:
NEON技术和其它ARM或第三方数据处理之前的区别
NEON和ARMv6 SIMD指令集的区别
ARMv6体系结构引入了一个很小的SIMD指令集,这些指令对打包在标准32位ARM通用寄存器中的多个16位或8位值进行操作。 这些指令允许某些操作以两倍或四倍的速度执行,而无需添加其他计算单元。
ARMv6 SIMD指令UADD8 R0, R1, R2
将一个32位寄存器中的四个8位值与另一个32位寄存器中的四个8位值相加,如图1-2所示。
该操作对封装在32位寄存器R1
和R2
中的向量中的四个8位元素(element)(称为通道lane
)执行并行加法运算,并将结果放入寄存器R0
中的向量中。
ARM NEON技术基于SIMD的概念,并支持128位向量操作,而不是ARMv6体系结构中的32位向量操作。
Cortex-A7和Cortex-A15处理器中默认含有NEON单元,但在其他ARMv7 Cortex-A系列处理器中也可能有。
特性 | ARMv7 NEON 扩展 | ARMv6 SIMD 指令集 |
---|---|---|
打包整形 | 8x8位,4x16位,2x32位 | 4x8位 |
数据类型 | 支持整数,如果处理器具有VFP单元,则支持单精度浮点运算 | 仅支持整数运算 |
并行操作 | 最多十六个(16 x 8位) | 最多四个(4 x 8位) |
专用寄存器 | 32个64位寄存器(或16个128位寄存器) 在与VFP单元共享的单独NEON寄存器文件上执行操作 |
使用32位通用ARM寄存器 |
流水线(Pipeline) | 具有专门为NEON执行而优化的专用流水线 | 使用与所有其他指令相同的流水线 |
NEON和其它SIMD的比较
SIMD处理不是ARM独有的,考虑其它的实现,并与NEON技术进行比较很有用。
Neon | x86 MMX/SSE | Altivec | |
---|---|---|---|
寄存器数量 | 32 x 64位 (或者视为16 x 128位) |
SSE2: 8 x 128-bit XMM (x86-32模式) x86-64模式下额外8个寄存器 |
32 x 128位 |
内存/寄存器操作 | 基于寄存器的3个操作数指令 | 寄存器和内存混合操作 | 基于寄存器的3和4操作数指令 |
打包数据的加载和存储 | 支持2、3、4个元素 | 不支持 | 不支持 |
在向量和标量寄存器之间移动数据 | 支持 | 支持 | 不支持 |
浮点支持 | 32位单精度 | 单精度或双精度 | 单精度 |
NEON和数字信号处理器的区别
许多ARM处理器还集成了数字信号处理器(DSP),或自定义的信号处理硬件,因此它们可以同时包含NEON单元和DSP。 使用NEON技术与使用DSP有一些区别:
NEON的特点:
DSP的特点:
NEON技术的架构支持
NEON扩展在某些处理器上被实现,这些处理器实现了ARMv7-A或ARMv7-R体系结构配置文件。
指令时序
NEON体系结构未指定指令时序。 一条指令可能需要不同数量的周期才能在不同的处理器上执行。即使在同一处理器上,指令时序也会变化。 导致变化的一个常见原因是指令和数据是否保留在缓存中。
对于仅含VFP系统的支持
一些指令是NEON和VFP扩展所共有的,这些称为共享指令。
VFP和NEON单元都使用CP10和CP11协处理器的指令空间中的指令,并且共享相同的寄存器文件,这导致两种操作之间会产生混淆:
但是,如果您的处理器同时具有NEON单元和VFP单元:
对于半精度扩展的支持
半精度指令仅在包含半精度扩展的NEON和VFP系统上可用。
半精度扩展(Half-precision extention)扩展了VFP和NEON体系结构。 它提供浮点和NEON指令,可在单精度(32位)和半精度(16位)浮点数之间执行转换。
对于融合乘加指令的支持
融合乘加指令是VFP和NEON扩展的可选扩展。 它提供了VFP和NEON指令,在执行乘法和加法时,带有一个的四舍五入步骤,因此与执行乘法随后执行加法相比,精度损失较小。
融合乘加指令仅在实现了融合乘加扩展的NEON或VFP系统上可用。 在VFPv4和先进的SIMDv2中包括对融合乘加指令的支持。
安全性和虚拟化
Cortex-A9和Cortex-A15处理器包括ARM安全扩展。 Cortex-A15处理器包括ARM安全扩展和虚拟化扩展。 这些扩展会影响您为NEON和VFP单元编写代码的方式。 这些扩展超出了本文的范围,有关更多信息,请参见《 ARM体系结构参考手册》 ARMv7-A和ARMv7-R。
未定义的指令
NEON指令(包括半精度和融合乘加指令)在不支持必要架构扩展的系统上,被视为未定义指令。
即使在具有NEON单元和VFP单元的系统上,如果未在CP15协处理器访问控制寄存器(CPACR)中启用这些指令,则这些指令也是被视为未定义的。 有关启用NEON和VFP单元的更多信息,请参见所编程处理器的《技术参考手册》。
对于ARMv6 SIMD指令集的支持
ARMv6架构添加了许多SIMD指令,以有效地实现多媒体算法的软件。 ARMv7不推荐使用ARM6 SIMD指令。
NEON技术基础
如果您熟悉ARM v7-A架构配置文件,您会注意到ARMv7内核是32位架构,并使用32位寄存器,但是NEON单元使用64位或128位寄存器进行SIMD处理。
之所以可以这样做,是因为NEON单元和VFP是ARMv7指令集的扩展,这些指令集在64位寄存器的单独寄存器文件中运行。 NEON和VFP单元完全集成到处理器中,并共享处理器资源以进行整数运算,循环控制和缓存。 与硬件加速器相比,这大大减少了面积并降低了功耗。 由于NEON单元使用与应用程序相同的地址空间,因此它还使用了更为简单的编程模型。
NEON组件的组件包括:
寄存器,向量,通道,元素
NEON指令和浮点指令使用相同的寄存器文件,称为NEON和浮点寄存器文件。 这与ARM核心寄存器文件不同。 NEON和浮点寄存器文件是一组寄存器的集合,可以将其作为32位,64位或128位寄存器进行访问。 一条指令可使用哪些寄存器取决于它是NEON指令还是VFP指令。 本文档将NEON和浮点寄存器称为NEON寄存器。 某些VFP和NEON指令在通用寄存器和NEON寄存器之间移动数据,或使用ARM通用寄存器来寻址存储器。
NEON寄存器的内容是相同数据类型的元素的向量。向量分为多个通道(lane),每个通道包含一个称为元素(element)的数据值。
通常,每个NEON指令都会导致n个操作并行发生,其中n是输入向量被划分为的通道(lane)数。 每个操作都包含在通道(lane)中。 从一个通道到另一个通道之间不会有进位或溢出。
NEON向量中的通道的数量取决于向量的大小以及向量中的数据元素:
64位NEON向量可包含:
128位NEON向量可包含:
元素的排列
图3显示向量中元素的顺序是从最低有效位开始的。 这意味着元素(element)0 使用寄存器的最低有效位。
图4展示了VADD.I16 Q0, Q1, Q2
指令如何从Q1
和Q2
中的向量执行八个通道的16位(8 x 16位 = 128位)整数(I
)元素的并行加法,并将结果存储在Q0
中。
寄存器重叠
图5显示了NEON和浮点寄存器文件,寄存器共享同样的内存,以不同的名称访问(Q或D或S)。
NEON单元把寄存器文件视作:
Q0
-Q15
。D0
-D31
。(在VFPv3-D16中是16个64位D寄存器)D<2n>
映射到Q
的低位那半部分。D<2n+1>
映射到Q
的高位那半部分。S<2n>
映射到D
的低位那半部分。S<2n+1>
映射到D
的高位那半部分。 所有这些寄存器都可以随时访问。软件不必在它们之间进行显式切换,因为所使用的指令确定了访问形式。在图6中,S,D和Q寄存器是同一寄存器Q0
的不同命名:
Q0
当做128位寄存器来访问。Q0
当做2个连续的64位寄存器: D0
和D1
来访问。Q0
里的32位S寄存器。但如果存在VPF单元,则可以将其作为S0
,S1
,S2
和S3
来访问。标量数据
标量是指单个值,而不是包含多个值的向量。 一些NEON指令使用标量操作数。 寄存器内的标量通过索引访问值向量。 访问向量的各个元素的数组符号为 D m [ x ] Dm[x] Dm[x]或 Q m [ x ] Qm[x] Qm[x],其中 x x x是向量 D m Dm Dm或 Q m Qm Qm中的索引。
指令VMOV.8 D0[3], R3
将寄存器R3
的最低有效字节移至寄存器D0
的第4字节。
NEON标量可以是8位,16位,32位或64位值。 除了乘法指令,访问标量的指令可以访问寄存器文件中的任何元素。
乘法指令仅允许使用16位或32位标量,并且只能访问寄存器文件中的前32个标量:
D0[x]
- D7[x]
,x的范围为0到3。D0[x]
- D15[x]
,x为0或1。NEON数据类型说明符
尽管ARM体系结构不需要处理器来实现VFP和NEON技术,但是这些扩展的编程中的共同功能意味着,支持VFP的操作系统只需很少或根本不需要修改,即可支持NEON技术。 但是,由于NEON单元在某些Cortex-A系列处理器中是可选的,因此您不能依靠NEON代码在所有处理器上均可工作。
在使用NEON或VFP指令并在给定处理器上编译代码之前,必须检查是否有NEON或VFP单元。
NEON和VFP指令中的数据类型说明符,由表示数据类型的字母组成。通常后跟表示宽度的数字。它们与指令助记符之间有一个点,例如VMLAL.S8
。
8位 | 16位 | 32位 | 64位 | |
---|---|---|---|---|
无符号整形 | U8 | U16 | U32 | U64 |
有符号整形 | S8 | S16 | S32 | S64 |
未指定类型的整数 | I8 | I16 | I32 | I64 |
浮点数 | 不可用 | F16 | F32 或 F | 不可用 |
多项式{0,1} | P8 | F16 | 不可用 | 不可用 |
多项式类型适用于在{0,1}上使用2次幂的有限域,或简单多项式的运算。
实现ARMv7A和ARMV-7R体系结构的处理器,可包括以下的VFP扩展之一:
64位D寄存器 | 有半精度扩展 | 没有半精度扩展 |
---|---|---|
16个 | VFPv3-D16-FP16 | VFPv3-D32 |
32个 | VFPv3-D32-FP16 (也叫做VFPv4,若带有融合乘加扩展) |
VFPv3-D32 (有时候就叫VFPv3,如果没有与其它扩展融合的可能) |
在 VFPv3-D16和VPFv3-D16-FP16版本中,VFP单元把NEON寄存器文件当成:
D0
-D15
,D寄存器被称为半精度寄存器,包含半精度浮点变量。S0
-S31
,S寄存器被称为单精度寄存器,包含单精度浮点变量或者32位整形变量。在VFPv3,VFPv3-D32,和VFPv3-D32-FP16版本中,VFP单元把NEON寄存器文件当成:
D0
-D31
。S0
-S31
,D0
-D15
与S寄存器S0
-S31
重叠。。表5显示了VFP指令集的可用数据类型。
16位 | 32位 | 64位 | |
---|---|---|---|
无符号整形 | U16 | U32 | 不可用 |
有符号整形 | S16 | S32 | 不可用 |
浮点数 | F16 | F32 (或F) | F64 (或D) |