????码带为了Neon - Part2:Dealing With Leftovers
在本系列的第1部分中,探讨了如何在Neon处理单元和内存之间传输数据。在本文中,我们处理一个经常遇到的问题:输入数据不是要处理的向量长度的倍数。你需要在数组的开头或结尾处,处理剩余的元素——在Neon上处理这个问题的最佳方法是什么?????
Leftovers(剩菜剩饭????)
使用Neon通常涉及这样的操作:处理长度为4~16个元素(位)的向量数据。通常你会发现你的数组不是该长度的倍数,然后不得不分别处理那些剩余的元素。
例如,你想要用Neon在每次迭代中加载、处理或存储8个元素,但是你的数组长度是21个元素。前2次迭代进行得很好,但是到了第3次,仅剩下5个元素要处理,你要怎么做?
Fixing Up(解决办法)
有3个办法来处理这些剩下来的东西,这些方法在需求、性能、代码大小上各不相同。下面依次列出这几种方法,最快的方法排在前面。
1. Larger Arrays(增大数组)
如果你可以改变你正在处理的数组的长度,可通过增加元素(padding elements)将数组的长度增加到向量大小的倍数。这可以让你在数据的末尾读取和写入数据,而不会破坏相邻的存储。
在上面的例子中,把数组的长度增加到24个元素,让第3次迭代得以完成而不会造成潜在的数据破坏。
Notes(注意)
- 分配更大的数组将会消耗更多内存。在短数组很多的时候,这种开销会变得非常显著。
- 为了不影响计算结果,在数组末尾新增元素后,这些元素应该被初始化为指定的值。例如(1)如果你正在对数组求和,新增元素必须被初始化为0,从而不影响计算结果。(2)如果正在寻找数组最小值,新增元素必须设到Max这么大。
- 在一些情况下,想要不影响计算结果,但是没办法找到合适的初始值,例如求数组均值的时候。
Code Fragment(代码片段)
以上图为例
@ r0 = 输入数组指针
@ r1 = 输出数组指针
@ r2 = 数组的数据长度
@ 我们可以假设数组长度比0要大,是个整形向量,比数组中数据的长度大或者相等
add r2, r2, #7 @ r2 = r2 + 7, 7的由来:向量长度-1
lsr r2, r2, #3 @ r2 = r2 / 8, 总长除以每一段的长度,算出有几段向量要处理(右移3就是除以8)
loop:
subs r2, r2, #1 @ r2 = r2 - 1, 剩余要循环的次数, 每次减一
vld1.8 {d0}, [r0]! @ 从r0指向的地址加载8个元素(位)到d0寄存器,!表示马上刷新r0指向下一块地址
...
... @ 处理加载到d0的数据
...
vst1.8 {d0}, [r1]! @ 写8个元素到输出数组,更新r1的值,指向下一块地址
bne loop @ 如果r2不为0,继续循环
2. Overlapping(重叠)
如果操作合适,可以用重叠(overlapping)来处理剩余元素。这涉及到两次处理数组中的一些元素。
在下图的例子中,第一次迭代处理[0-7]的元素,第二次处理[5-12]的元素,第三次处理[13-20]的元素。注意[5-7]的元素在第1和第2个向量中重复了,被处理了2次。
Notes(注意)
- 当对输入数据的操作,不随操作次数的变化而变化时,才能使用重叠(overlapping),操作必须是幂等的。例如,(1)当你正在找数组的最大元素时,就可以使用。(2)对数组求和就不能使用,重叠部分将被计数2次。
- 数组中元素的数量必须能够填满至少1个完整的向量。
Code Fragment(代码片段)
@ r0 = 输入数组指针
@ r1 = 输出数组指针
@ r2 = 数组的数据长度
@ 假设操作是幂等的,数组的长度至少为1个向量的长度
ands r3, r2, #7 @ 求处理完所有整长的向量后,数组还剩多少剩余没法整除的。即求r2与8的余数
@ r3 = r2 & 7, 7的由来:向量长度-1.
@ and:逻辑与. ands:逻辑与之后立即更新cpsr中的标志位.
beq loopsetup @ 如果`ands`操作的结果是0, 数组的长度就是向量长度的整数倍
@ 所以没有重叠, 可以调到循环哪里开始处理
@ 否则单独分开处理第一个向量
vld1.8 {d0}, [r0], r3 @ 从数组加载第一组8个元素, 根据元素重叠数量数量更新指针, r3就是重叠的数量
...
... @ 处理加载到d0的数据
...
vst1.8 {d0}, [r1], r3 @ 写8个元素到输出数组, 更新输出数组指针的位置
loopsetup: @ 循环前的处理
lsr r2, r2, #3 @ r2 = r2 / 8, 算算有多少个向量需要处理(即循环多少次)
loop: @ 像前一个example一样正常循环了
subs r2, r2, #1 @ r2 = r2 - 1, 剩余要循环的次数, 每次减一
vld1.8 {d0}, [r0]! @ 从r0指向的地址加载8个元素(位)到d0寄存器,!表示马上刷新r0指向下一块地址
...
... @ 处理加载到d0的数据
...
vst1.8 {d0}, [r1]! @ 写8个元素到输出数组,更新r1的值,指向下一块地址
bne loop @ 如果r2不为0,继续循环
解读:也就是首先对重叠部分进行处理、指针偏移,然后就跟上一个例子一样开始正常循环。
3. Single Elements(单个元素)
Neon提供加载和存储的操作,可以处理向量里的单个元素。通过这个方法,可以加载包含1个元素的部分向量,对其进行操作,然后把该元素写回到内存中。
对于下图的问题,前两次迭代正常执行,处理[0-7]和[8-15]的元素。第三次迭代只需要处理5个元素。他们在单独的循环中处理:加载、处理和存储单个元素。
Notes(注意)
- 这个方法比之前的两种方法慢,因为每个元素都要被单独地加载、处理和存储。
- 这种处理剩余元素的方式要两次循环:1次是处理向量,第2次是处理单个元素。这导致代码的数量会翻倍。
- Neon加载单数据仅改变目标元素的值,向量的其余部分保持不变。如果某些向量化的计算指令如
VPADD
,需要跨向量运作,那在载入第一个元素之前,该寄存器必须先被初始化。
Code Fragment(代码片段)
未完待续。。。。。。
上一篇: 没想到是流氓
下一篇: HTML编写网页(上)