Reference:
NEON
技术是用于 Arm Cortex-A
系列处理器的先进 SIMD(单指令多数据)架构。它可以加速多媒体和信号处理算法,如视频编码器/解码器、2D/3D图形、游戏、音频和语音处理、图像处理、电话和声音。
NEON 指令执行“打包 SIMD”处理:
Armv6 | Armv7-A | Armv8-A AArch64 |
---|---|---|
SIMD extension | NEON | NEON |
在32位通用ARM寄存器上操作 | 独立的寄存器库,32x64位NEON寄存器 | 独立的寄存器库,32x128位NEON寄存器 |
8位或16位整数 | 8/16/32/64位整数 | 8/16/32/64位整数 |
每条指令进行2x16位/4x8位操作 | 单精度浮点数 | 单精度浮点数、双精度浮点数 |
– | 每条指令最多16x8位操作(16x4吧,待确认) | 每条指令最多16x8位操作 |
Armv7-A 和 AArch32 具有相同的通用 Arm 寄存器 - 16 16 16 x 32 32 32 位通用 Arm 寄存器(R0-R15)。
Armv7-A 和 AArch32 具有 32 x 64 位 NEON 寄存器(D0-D31)。这些寄存器也可以看作是 16 × 128 位寄存器(Quad-word
, Q0-Q15)。每个 Q0-Q15 寄存器映射到一对D寄存器,如下图所示。
相比之下,AArch64 具有
31
31
31 个
64
64
64 位通用 Arm 寄存器和
1
1
1 个具有不同名称的特殊寄存器,这取决于使用它的上下文。这些寄存器可以被看作是
31
31
31 个
64
64
64 位寄存器(X0-X30)或
31
31
31 个
32
32
32 位寄存器(W0-W30)。
AArch64 具有
32
32
32 x
128
128
128 位 NEON 寄存器(V0-V31,Vector Registers
)。这些寄存器也可以看作是
32
32
32 位 Sn寄存器(Single-word Registers)
或
64
64
64 位 Dn寄存器(Double-word Registers)
。
(也就是说,V 寄存器有 128 位,D 寄存器 64 位,S寄存器 32 位,可以将 V 寄存器拆开使用)
变量命名方式:
baseWxLxN_t
uint8x16_t
、uint8x16x3_t
函数命名方式:
ret v[p][q][r]name[u][n][q](args)
热点函数涉及到大量 IO 读写操作时,数据的内存地址尽量与 NEON 数组或系统位数对齐,如32位对齐,可降低访问开销;
重点优先搞 NEON 指令并行计算,能大幅降低开销;
大部分的 NEON 问题会出在存取、移动指令的滥用、混乱使用上(neon的寄存器和普通arm的寄存器是分开,也就是说arm的普通指令和neon指令之间不可以有过多的数据交换,但是sse没有这个限制?待验证);
for
循环:
for (b = 0; b < num; b++)
:for (b = 0; b < num - 3; b += 4)
;for (b = num - 1; b >= 3; b -= 4)
;数组索引取值:
内存使用:
指令运算
C 语言编码级考虑
switch
比 if else
快,而且代码整洁。深入理解计算机系统
NEON 与 SSE 在寄存器处理数据时有一些区别。在 NEON 中,通常需要先将要处理的数据加载到 NEON 寄存器,然后执行 SIMD 操作。这与 SSE 有一些不同,因为 SSE 寄存器(XMM 寄存器)可以直接与内存交互(这一步可能格外耗时,需要特别注意)。在 NEON 中,加载数据到 NEON 寄存器通常包括以下步骤:
然后,您可以在 NEON 寄存器上执行 SIMD 操作,例如矢量加法、矢量乘法等。
这与 SSE 不同,因为 SSE 寄存器可以直接从内存加载数据,而不需要中间步骤。这可以在 SSE 指令中实现,而不需要将数据先加载到通用寄存器中。
总之,NEON 需要额外的步骤来加载数据到寄存器,然后才能进行 SIMD 操作,而 SSE 可以更直接地在寄存器中操作数据。这是因为不同架构和指令集设计的差异。
考虑处理器如何集成 NEON 技术的实现定义方面,因为针对特定处理器优化的指令序列可能在不同的处理器上具有不同的时序特征,即使 NEON 指令周期时序相同。
为了从手写的 NEON 代码中获得最佳性能,有必要了解一些底层硬件特性。特别是,程序员应该意识到流水线和调度问题、内存访问行为和调度危害。
Cortex-A8 和 Cortex-A9 处理器共享相同的基本 NEON 管道,尽管在如何将其集成到处理器管道中存在一些差异。Cortex-A5 处理器包含一个完全兼容的简化 NEON 执行管道,但它是为尽可能最小和最低功耗的实现而设计的。
TLB(Translation Lookaside Buffer)
是计算机系统中的一种硬件缓存,用于加速虚拟地址到物理地址的转换过程。TLB 的每个条目称为 TLB entry(TLB条目)
,它存储了一组虚拟地址和相应的物理地址之间的映射关系。TLB 通常位于 CPU 内部,用于提高内存访问的速度。
当程序执行时,CPU 需要将虚拟地址(由程序使用)转换为物理地址(在内存中实际存储数据的地址)。这个地址转换通常由操作系统的内存管理单元(MMU)
来执行。MMU 将虚拟地址映射到物理地址,并在需要时将这些映射关系存储在 TLB 中,以便以后的访问可以更快地完成,而无需再次执行昂贵的地址转换操作。
TLB entry 通常包括以下信息:
当 CPU 需要访问内存中的数据时,它首先查看 TLB 来查找虚拟地址和物理地址之间的映射关系。如果找到了匹配的 TLB entry,那么物理地址将用于访问内存,这将显著提高内存访问速度。如果没有找到匹配的 TLB entry,CPU 将向 MMU 请求执行地址转换,并将结果存储在 TLB 中以供将来使用。
总之,TLB entry 是 TLB 中存储的虚拟地址到物理地址映射的单元,用于加速计算机内存访问的过程。这有助于提高系统的性能和效率。
L1 和 L2 通常是指计算机的缓存层次结构中的两个不同级别的缓存:
L1 Cache(一级缓存)
:(Instruction Cache)
和 数据缓存(Data Cache)
。指令缓存存储 CPU 指令,而数据缓存存储处理数据。L2 Cache(二级缓存)
:这两个缓存级别的存在是为了提高计算机的性能。L1 缓存专注于存储最常用的数据和指令,因此它们可以更快地被 CPU 核心访问。如果数据不在 L1 缓存中,CPU 将在 L2 缓存中查找,如果还不在,那么将从主内存中获取数据。通过在多个缓存层之间进行数据访问,计算机可以更有效地管理内存访问,从而提高整体性能。
NEON 单元很可能会处理大量数据,例如数字图像。一个重要的优化是确保算法以最适合缓存的方式访问数据。这样可以从 L1 和 L2 缓存中获得最大的命中率(hit rate)
。考虑活动内存位置的数量也很重要。在 Linux 下,每个 4KB 的页面都需要一个单独的 TLB 条目。Cortex-A9 处理器有多个
32
32
32 个元素的 micro-TLB 和一个
128
128
128 个元素的主 TLB,之后它将开始使用 L1 缓存来加载页表条目(page table entry)。一种典型的优化是安排算法以适当的大小处理图像数据,以最大限度地提高缓存和 TLB 命中率。
支持 交错(interleaving)
和 反交错(de-interleaving)
的指令可以为性能改进提供很大的空间。VLD1
从内存加载寄存器,没有去交错。然而,其他 VLDn
操作使我们能够加载、存储和反交错包含两个、三个或四个相同大小的
8
8
8、
16
16
16 或
32
32
32 位元素的结构。VLD2
加载两个或四个寄存器,去交错的偶数和奇数元素。例如,这可以用于分割左通道和右通道立体声音频数据,如下图所示。类似地,VLD3
可用于将 RGB 像素分割为单独的通道,相应地,VLD4
可用于 ARGB 或 CMYK 图像。
上图显示了用 VLD2.16
(
16
16
16 字节) 从 R1 指向的内存中加载两个 NEON 寄存器。这在第一个寄存器中产生
4
4
4 个
16
16
16 位元素,在第二个寄存器中产生
4
4
4 个
16
16
16 位元素,相邻的成对左值和右值被分隔到每个寄存器中。