例如,考虑一个包含21个数据元素的输入数组,每个元素是一个16位整数。您希望使用Neon来处理这个数组中的数据。Neon寄存器有128位宽,所以一次可以处理8条16位的数据通道。在两次迭代中,Neon代码可以处理16 (2 x 8)个数据元素。然而,这留下了5个剩余的数据元素要在最后的迭代中处理。这五个剩余的数据元素不足以完全填满一个Neon寄存器。
1.用填充扩展数组
如果可以更改数组的大小,则可以使用填充元素将数组的长度增加到向量大小的下一个倍数。这允许您在不损坏相邻存储的情况下读取和写入超出数据末尾的数据。
限制:要注意选择不影响计算结果的填充值,否则放弃该方法。比如:要对数组值求和,可以使用零填充值;如果正在查找数组中的最小值,可以使用数据元素可以包含的最大值的填充值。
2.重叠数据元素
重叠意味着对数组中的某些元素处理两次。在示例案例中,使用重叠的迭代将遵循以下步骤:
1.第一次迭代处理元素0到7。
-
第二次迭代处理元素5到12。
-
第三个也是最后一个迭代处理元素13-20。
也就是说,元素5到7(即第一个向量和第二个向量之间的重叠部分)被处理两次。
限制:只有当应用于输入数据的操作不随应用操作的次数而变化时,才能使用重叠。专业术语是说这个运算必须是幂等的。例如,如果您试图查找数组中的最大元素,则可以使用重叠。这是因为最大值出现多次并不重要。但是,如果要对数组求和,则不能使用重叠。这是因为重叠的元素会被计数两次。
3.将剩余元素作为单个元素处理
Neon提供了加载和存储指令,可以对vector中的单个元素进行操作。您可以使用这些指令来加载包含一个元素的部分向量,对该部分向量进行操作,然后将该元素写回内存。
在示例中,使用单个元素的迭代将遵循以下步骤:
1.前两个迭代照常执行,处理元素0到7和8到15。
-
第三次迭代只需要处理5个元素。一个单独的循环处理这些元素,它加载、处理和存储单个元素。
缺点就是这种方法比前两种方法慢。这是因为每个剩余元素都必须单独加载、处理和存储。
这三种方法可以根据您自己的特殊需要加以改进或调整,具体如下:
· 选择何时处理剩余元素
可以选择在处理数组的开始或结束时应用重叠和单元素技术,具体取决于哪个更适合应用程序。
· 地址对齐
Armv8-A体系结构允许任意排列多种类型的加载和存储访问。然而,这条规则也有例外。例如,加载和存储地址应该与缓存线对齐,以允许更有效的内存访问。
· 使用A64基指令代替Neon指令
在单元素方法中,您可以使用Arm A64基本指令和通用寄存器来操作每个单个元素,而不是使用Neon。但是,同时使用基本A64指令和Neon SIMD指令写入同一内存区域会降低性能。来自Arm管道的写操作被延迟,直到来自Neon管道的写操作完成。一般来说,你应该避免从Arm和Neon代码中写入相同的内存区域,特别是相同的缓存行。
本案例参考来自arm官方文档《neon_programmer_guide_for_armv8-a_coding_for_neon》