原文地址数组
在这里,咱们将处理一种常见的问题:输入的数据不是向量长度的倍数,须要处理数组开头或者结尾的剩余数据时。这种状况下,NEON能够如何处理。app
使用NEON一般都是操做长度为4到16位的数据向量。常常地,你将会发现数组并非那些长度的倍数,你必须单独处理这些剩余的数据。ide
例如,你想要在每一个迭代中用NEON加载、处理及存储8个数据,可是你的数组是21个数据长度。前两次的迭代都可以正常进行,可是第三个迭代中,只有5个数据须要处理时,应该怎么办。oop
有三种方法能够处理这些剩余数据。根据不一样的需求、性能及代码大小,每种方法都不尽相同。这些方法以下,速度越快的越靠前。性能
若是你可以改变你将要处理的数组的大小,用填充数据的方式增长数组大小至下一个向量大小的倍数。这可让你在不影响邻近存储的状况下读取和写入数据。优化
在下面的例子中,增长数组大小至24个数据,使得第三次迭代能够完成。spa
分配更大的数组会消耗更多的内存。若是有不少短数组,这样的分配会带来更多的消耗。
在末尾进行填充的数据须要被初始化,一般将其初始化为不影响计算结果的数据。例如,若是你在对数组进行求和,新的数据必须初始化为0。若是你在查找最小值,新的元素必须设置为最大值。
在一些状况下,初始化不影响填充数据可能不太好实现,如当须要查找数组数据的范围时。code
@ r0 = input array pointer @ r1 = output array pointer @ r2 = length of data in array @ We can assume that the array length is greater than zero, is an integer @ number of vectors, and is greater than or equal to the length of data @ in the array. add r2, r2, #7 @ add (vector length-1) to the data length lsr r2, r2, #3 @ divide the length of the array by the length @ of a vector, 8, to find the number of @ vectors of data to be processed loop: subs r2, r2, #1 @ decrement the loop counter, and set flags vld1.8 {d0}, [r0]! @ load eight elements from the array pointed to @ by r0 into d0, and update r0 to point to the @ next vector ... ... @ process the input in d0 ... vst1.8 {d0}, [r1]! @ write eight elements to the output array, and @ update r1 to point to next vector bne loop @ if r2 is not equal to 0, loop
若是操做容许的话,剩余数据能够经过重叠操做进行处理。这将使得一些数据被处理两次。orm
在上述的例子中,第一次迭代将处理0到7的数据,第二次迭代处理5到12,第三次迭代处理13到20.须要注意5到7的数据被处理了两次。blog
重叠操做只有在数据不受访问次数的影响时才能被使用,该操做必须是幂等性的。如,当你须要在数组中查找最大值的时候,能够使用这种策略。当你须要对一个数组进行求和时,重叠的数据会被重复计算。
数据的个数必须至少可以填充一个完整的向量。
@ r0 = input array pointer @ r1 = output array pointer @ r2 = length of data in array @ We can assume that the operation is idempotent, and the array is greater @ than or equal to one vector long. ands r3, r2, #7 @ calculate number of elements left over after @ processing complete vectors using @ data length & (vector length - 1) beq loopsetup @ if the result of the ands is zero, the length @ of the data is an integer number of vectors, @ so there is no overlap, and processing can begin @ at the loop @ handle the first vector separately vld1.8 {d0}, [r0], r3 @ load the first eight elements from the array, @ and update the pointer by the number of elements @ left over ... ... @ process the input in d0 ... vst1.8 {d0}, [r1], r3 @ write eight elements to the output array, and @ update the pointer @ now, set up the vector processing loop loopsetup: lsr r2, r2, #3 @ divide the length of the array by the length @ of a vector, 8, to find the number of @ vectors of data to be processed @ the loop can now be executed as normal. the @ first few elements of the first vector will @ overlap with some of those processed above loop: subs r2, r2, #1 @ decrement the loop counter, and set flags vld1.8 {d0}, [r0]! @ load eight elements from the array, and update @ the pointer ... ... @ process the input in d0 ... vst1.8 {d0}, [r1]! @ write eight elements to the output array, and @ update the pointer bne loop @ if r2 is not equal to 0, loop
NEON提供了可以在向量中处理单个数据的加载及存储指令。经过这些,你能够加载一个包含一个数据的向量,进行操做,而且写入内存中。
在上述的例子中,前两次的迭代都正常进行,处理0到7,8到15的数据。第三次迭代须要处理5个数据,能够在一个单独的循环中进行处理,每次循环处理一个数据。
该方法比前面的方法都慢,由于每一个数据必须被单独的加载、处理和存储。
处理剩余数据须要两次循环,一次以向量为单位,第二次以单个数据为单位。这将增大代码大小。
NEON单数据加载只改变目标数据的值而不影响其余数据。若是你在向量化的计算中涉及到操做向量的指令,如VPADD,寄存器在加载第一个单数据时必须被初始化。
@ r0 = input array pointer @ r1 = output array pointer @ r2 = length of data in array lsrs r3, r2, #3 @ calculate the number of complete vectors to be @ processed and set flags beq singlesetup @ if there are zero complete vectors, branch to @ the single element handling code @ process vector loop vectors: subs r3, r3, #1 @ decrement the loop counter, and set flags vld1.8 {d0}, [r0]! @ load eight elements from the array and update @ the pointer ... ... @ process the input in d0 ... vst1.8 {d0}, [r1]! @ write eight elements to the output array, and @ update the pointer bne vectors @ if r3 is not equal to zero, loop singlesetup: ands r3, r2, #7 @ calculate the number of single elements to process beq exit @ if the number of single elements is zero, branch @ to exit @ process single element loop singles: subs r3, r3, #1 @ decrement the loop counter, and set flags vld1.8 {d0[0]}, [r0]! @ load single element into d0, and update the @ pointer ... ... @ process the input in d0[0] ... vst1.8 {d0[0]}, [r1]! @ write the single element to the output array, @ and update the pointer bne singles @ if r3 is not equal to zero, loop exit:
重叠和单数据处理技术可以应用在数组的起始和结束,上面的代码可以根据须要修改。
加载和存储地址必须对齐到cache,容许更有效的内存存取。
在Cortex-A8这要求至少16字的对齐。若是没法对输入和输出数组进行对齐,你就必须处理数组最开始的部分(须要对齐的部分)及结尾的部分(未完成的向量)。
当对齐数据访问时,记得在加载及存储指令中使用:64 或者:128 或者:256地址限定符来优化性能。你能够对比须要处理加载和存储的时钟周期的个数,使用在 Technical Reference Manual 中的数据。
Here’s the relevant page in the Cortex-A8 TRM.
在单个数据中,你能够使用ARM指令对每一个数据进行操做。然而,用ARM和NEON指令存储到内存的相同区域会下降性能,ARM流水线会被阻塞直到NEON流水线的完成。
通常地,你在代码中能够避免写到相同的内存。