转自ARM官网 https://developer.arm.com/architectures/instruction-sets/simd-isas/neon
原文链接:[Coding for Neon - Part 1: Load and Stores]
https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/coding-for-neon—part-1-load-and-stores?_ga=2.76228754.624776649.1595923255-241183949.1595923255
目录:
Coding Neon - Part1:Load and Stores
Coding Neon - Part2:Dealing With Leftovers
Coding Neon - Part3:Matrix Multiplication
Coding Neon - Part4:Shifting Left and Right
Coding Neon - Part5:Rearranging Vectors
Arm的Neon技术是一种64/128位混合SIMD架构,旨在加速多媒体和信号处理应用程序的性能,包括视频编解码、音频编解码、3D图形、语音和图像处理。👍
本文是系列文章的第一部分,教大家如何用汇编语言为Neon编写SIMD(Single Instruction Multiple Data单指令集多数据)代码。本系列将涵盖Neon的入门、高效使用,以及为更有经验的码农提供指示。我们将从内存操作开始,以及如何使用灵活的加载和存储置换指令。
An Example
给你一个24位的RGB图像,像素在内存中排列为RGBRGB...
,现在需要做一个简单的图像处理操作,例如交换红色和蓝色通道。如何使用Neon高效地做到这一点?
用一个加载操作将RGB数据从内存线性地拉到寄存器,这种交换红/蓝的方式看起来很愚蠢。
对于这种RGB排列的输入,编写通道交换的代码不会很优雅,例如掩码(masks)、位移(shifting)、组合(combing)等,不太可能有效率。
Neon提供了结构化加载和存储指令,为上述情况提供帮助。它们从内存中提取数据,将这些值同时分离到不同的寄存器中。对于这个例子你可以用VLD3
指令,在加载红、绿、蓝的同时将它们拆分。
然后使用指令VSWP d0, d2
交换红色和蓝色寄存器,再用类似命名的存储指令(VST3
),将交织后的数据写回内存。
The Details
Overview(概述)
Neon结构将这些从内存中读取的数据,加载到64位Neon寄存器中,同时可以选择性地将数据交错(deinterleaving)(如上面的例子)。存储的工作原理也类似,在写入内存之前,再次交错(reinterleaving)来自寄存器的数据。
Syntax(语法)
结构加载和存储指令的语法由5个部分组成。
- 助记指令符(Instruction mnemonic),
VLD
表示加载,VST
表示存储。 - 数字交织模式(Interleave pattern),在每个结构中,对应元素之间的间隔。
- 元素类型(Element type),指定被访问元素的位数。
- 一组要读或者写的Neon寄存器列表(Neon register list),最多可以列出4个寄存器,主要看交织模式的选择。
- Arm地址寄存器(Arm address register),包含了内存中要访问的地址。访问后可以更新地址。
Interleave Pattern(交错模式)
指令可以用于加载、存储和解交错(deinterleave)一块结构,结构中包含1到4个大小相等的元素(也就是寄存器列表的数量?),这些元素通常是Neon支持的8、16、32位宽度数据。
VLD1
,是最简单的形式,它从内存中加载1到4个寄存器的数据,没有解交错(deinterleaving)。处理一组没有交错(non-interleaved)的数据的时候使用这种模式。VLD2
,加载2到4个寄存器的数据,将偶数和奇数元素解交错(deinterleaving)到这些寄存器中。使用此选项可将立体声音频数据分为左右声道。VLD3
,加载3个寄存器的数据,并且解交错(deinterleaving),用于将RGB像素分割为通道。VLD4
,加载4个寄存器的数据,并且解交错(deinterleaving),用于处理ARGB的图像数据。
存储指令支持同样的选项,并且在写入内存之前,交错(interleave)来自寄存器的数据。
Element Types(元素类型)
根据指令指定的大小,加载或存储交错的元素。例如用VLD 2 .16
加载2个Neon寄存器的数据,会在第一个寄存器中产生4个16位元素,在第二个寄存器中产生4个16位元素,相邻的一对(奇和偶)分离到每个寄存器中。(看图↓↓↓)
若将元素大小改为32位(以上图为例把16位改成32位),则会加载同样数量的数据,但是每个Neon寄存器里面只有2个元素。另外,仍然像上面一样,同时又分为奇数和偶数元素。(看图↓↓↓)
元素大小也会影响字节序处理。通常,如果为加载和存储指令指定了正确的元素大小,则将以适当的顺序从内存中读取字节,以同样的代码在大端或者小端系统上运行。(这段不知道想讲什么,反正要注意大小端系统的字节顺序)。
最后,元素的大小会影响指针对齐。与元素大小对齐通常可以提供更好的性能,或者你的目标操作系统要求这样做。例如,加载32位元素的时候,第一个元素的地址至少对齐到32位。
Single or Multiple Elements(单个或多个元素)
除了加载多个元素外,结构加载还可以通过解交错(deinterleaving)从内存中读取单个元素,将其读取到Neon寄存器的所有通道或者单个通道,而其它通道保持完整。
当你需要根据分散在内存中的数据构造向量时,后一种形式很有用。
存储也相似,通过交错(interleaving),为写入单个或多个元素提供支持。
Addressing(寻址)
结构加载和存储指令支持3种形式,用于指定寻址。
- 寄存器:
[{,:}]
这是最简单的形式,数据将会被加载或者存储到指定的地址。 - 后面加个叹号:
[{,:}]!
这种方式在加载或存储数据后更新指针,准备加载或存储下一个元素。指针增量等于指令加载或存储的字节数。 - 后面加个逗号:
[{,:}],
在内存访问后,指针的增量等于Rm
寄存器的值。在读取或写入以固定宽度分隔的一组元素时,这种方式很有效。例如,读取一张图像中的垂直线。
你还可以使用可选的“:
”参数,为Rn
寄存器中的指针指定对齐方式,这通常会加速内存访问。
Other Loads and Stores(其它加载和存储)
在本文中,我们仅讲述了加载和存储,Neon还提供了:
VLDR
和VSTR
加载和存储单个寄存器(以64位值)。VLDM
和VSTM
以64位值加载多个寄存器。对于从栈上存取寄存器很有用。
==============================================================
有关支持的加载和存储操作的更多详细信息,请参见《 Arm Architecture参考手册》。 有关说明的详细循环计时信息,请参见每个内核的《技术参考手册》。