ARM 的 NEON技术是一种64/128位的混合 SIMD (single instruction multi-data, 多数据操作单指令)构架,是为多媒体和信号处理应用而设计,包括视频编解码,3D图像,语音和图像处理。
本系列帖子是关于如何编写汇编语言的SIMD代码来使用NEON技术,这是本系列的第一篇。本系列的内容包括如何开始使用NEON,如何高效地使用它,以及成为该方面有经验程序员的一些启示和建议。我们将从内存操作开始,学习如何灵活地使用加载和存储指令来完成数据交叉排列的任务。
我们从一个实例开始。你有一张24-bit RGB 图像,它的像素在内存里排列如下R,G,B,R,G,B···你现在要实现一个简单的图像处理操作,比如切换红色和蓝色通道。我们该如何使用NEON来高效地完成任务呢?
依次地从内存里读取RGB到寄存器来实现红色和蓝色互换显得有些不太合适。
使用这种读取方式来实现交换的代码将会变得不简洁,不流畅,不紧凑。同时也不会高效。
NEON提供的结构化读取和存储指令可在这种情况下给予帮助。这些指令将数据从内存中取出,同时将这些数据分散到不同的寄存器向量。在本例中,你可以使用VLD3在读取数据的同时分开红、绿、蓝分量。
现在交换红蓝寄存器向量的值(VSWP d0, d2)然后将数据写回内存。使用相似的指令VST3,交叉存储。
通过反交叉优化,NEON结构化读取数据从内存到64位NEON寄存器。存储过程也类似,交叉存储将数据从寄存器写到内存。
结构化读取和存储指令的语法由5部分构成
指令助记符,VLD 表示读取,或者VST表示存储。
交叉模式的数值,表示相应元素之间的间隔。
元素类型,指定访问元素的bit位数。
用来读写的64位NEON寄存器集合。最多可以列出4个寄存器,取决于交叉模式。
ARM地址寄存器,存放访问的内存地址。地址可以随访问而更新。
这些指令可以用来读取,存储。反交叉结构包括一个到四个相同大小的数据,NEON支持数据宽度为8,16和32位。
VLD1 是最简单的形式。该指令可以从内存读取1到4个寄存器的数据,不进行反交叉。当处理无交叉的数组时可以使用这种形式。
VLD2 读取2个或者4个寄存器的数据,将奇数和偶数序号的元素反交叉地存储到寄存器。这种形式可以用于将立体音频数据分割到左右声道。
VLD3 读取3个寄存器的数据并进行反交叉。非常有效地分割RGB像素到各自通道。
VLD4 读取4个寄存器的数据并进行反交叉。用来处理ARGB图像数据。
存储指令支持相同的操作,只是它是将数据交叉地从寄存器写到内存而已。
交叉读取和存储的元素类型取决于指令中指定的size。例如,使用 VLD2.16读取数据到两个NEON寄存器的结果是:第一个寄存器中存放4个16位元素,第二个寄存器也存放4个16位元素,它们相邻的一对(奇、偶)被拆分到不同寄存器。
将元素大小改为32位会导致相同数量的数据被读取,但现在每个向量只有两个元素,同样也被拆分为奇偶元素。
元素大小也会影响字节顺序。通常情况下,如果你在指令中正确地指定了元素大小,无论是大端还是小端系统都会按照合适的顺序来从内存中读取数据。
最后,元素大小也会影响指针对齐。按照数据元素大小进行对齐通常会获得更好的运行效果,你的目标操作系统可能会有这样的要求。例如,要读取32位数据元素时,至少要求第一个元素的地址为32位对齐。
除了可以读取多个元素之外,结构化读取还可以从内存中读取单个元素并将其交叉化后送到多个通道的每一个NEON寄存器,或者单个通道(不影响其他通道)。
当你要从分散的内存数据中构建一个向量时,后面一种形式会很有帮助
存储也类似,支持交叉存储单个和多个元素。
结构化读取和存储指令支持三种形式的寻址:
寄存器寻址: [ {, :}]
这是最简单的寻址方式。数据将被读入和存储到指定的地址。
寄存器更新寻址 [{, :}] !
使用这种方式在读取和存储之后指针自动指向下一个要读取或存储的元素。增加量等于指令读或写的字节数。
寄存器后索引寻址 [ {, :}]
当访问内存之后,指针的增加量为寄存器Rm的值。当你读写一组被固定长度所分割的元素时这种形式会很有帮助。例如,当你读取图像某一列的数据时。
你也可以通过Rn为指针指定对齐,使用:选项参数。这样做可以挺高内存的访问速度。
在本贴中,我们仅仅对结构化读取和存储进行了分析。NEON还支持:
VLDR 和 VSTR 以64位值来读取和存储单个NEON寄存器,
VLDM 和VSTM 以64位值来加载和存储多个NEON寄存器,适用于从堆栈中存储和取回寄存器的值。