欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

????码带为了Neon - Part2:Dealing With Leftovers

程序员文章站 2022-04-19 16:12:06
...

  在本系列的第1部分中,探讨了如何在Neon处理单元和内存之间传输数据。在本文中,我们处理一个经常遇到的问题:输入数据不是要处理的向量长度的倍数。你需要在数组的开头或结尾处,处理剩余的元素——在Neon上处理这个问题的最佳方法是什么?????

Leftovers(剩菜剩饭????)

  使用Neon通常涉及这样的操作:处理长度为4~16个元素(位)的向量数据。通常你会发现你的数组不是该长度的倍数,然后不得不分别处理那些剩余的元素。
  例如,你想要用Neon在每次迭代中加载、处理或存储8个元素,但是你的数组长度是21个元素。前2次迭代进行得很好,但是到了第3次,仅剩下5个元素要处理,你要怎么做?

Fixing Up(解决办法)

  有3个办法来处理这些剩下来的东西,这些方法在需求、性能、代码大小上各不相同。下面依次列出这几种方法,最快的方法排在前面。

1. Larger Arrays(增大数组)

  如果你可以改变你正在处理的数组的长度,可通过增加元素(padding elements)将数组的长度增加到向量大小的倍数。这可以让你在数据的末尾读取和写入数据,而不会破坏相邻的存储。
  在上面的例子中,把数组的长度增加到24个元素,让第3次迭代得以完成而不会造成潜在的数据破坏。
????码带为了Neon - Part2:Dealing With Leftovers

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次。
????码带为了Neon - Part2:Dealing With Leftovers

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个元素。他们在单独的循环中处理:加载、处理和存储单个元素。
????码带为了Neon - Part2:Dealing With Leftovers

Notes(注意)

  • 这个方法比之前的两种方法慢,因为每个元素都要被单独地加载、处理和存储。
  • 这种处理剩余元素的方式要两次循环:1次是处理向量,第2次是处理单个元素。这导致代码的数量会翻倍。
  • Neon加载单数据仅改变目标元素的值,向量的其余部分保持不变。如果某些向量化的计算指令如VPADD,需要跨向量运作,那在载入第一个元素之前,该寄存器必须先被初始化。

Code Fragment(代码片段)

未完待续。。。。。。

相关标签: Neon