在编写NEON代码时,你可以发现,在寄存器中的数据有些时候并不是跟算法要求的那种格式。你可能需要在向量中重组这些数据使得后续的计算能够自动将正确的部分加起来,或者传输到你函数中的数据是以一个奇怪的方式呈现的,然后在你使用SIMD代码时,这些数据需要被重新排序。
这种重新排序的方式叫做置换。置换指令重组从单个或者多个寄存器中选择的独立数据,并组成一个新的向量。
在你开始使用这些置换指令时,考虑你是否真的需要使用它们。
置换指令类似于移动指令,特别是在使用CPU时钟周期准备数据而不是处理数据。
你的代码只有当它使用最少的周期完成一个任务时,才是速度最优的;如果需要优化,移动和置换指令通常是可以优先考虑的地方。
你如何避免不必要的置换?这里有一些选择:
如果你已经考虑了这些方面,但是没有能够把你的数据设置为正确的格式,尝试使用置换指令吧。
NEON提供很多置换指令,从基本的反转到任意向量重组。简单的置换指令可以通过一个指令周期执行完毕,而复杂的指令则通过很多个指令周期,并且可能需要设置额外的寄存器。正如往常的一样,基准测试并清晰描述你的代码检查处理器的参考文档获取更多的性能上的细节。
VMOV and VSWP: Move and Swap 移动及交换
VMOV和VSWP 是最简单的置换指令,复制整个寄存器的内容到另外一个寄存器,或者交换一对寄存器中的值。
尽管你可能不把它们当成置换指令,它们可以用来修改两个D寄存器(一个Q寄存器)中的值;例如 VSWP d0, d1 交换q0中的值。
VREV: Reverse 反转
VREV 反转向量中的8,16或者32位数据,有以下三个变量:
使用VREV来反转数据的字节顺序,重组颜色分量或者交换声道数据。
VEXT: Extract 提取
VEXT 从一对已有的向量中提取出一个新的向量。新向量中的数据是从第一个操作数的头部和第二个操作数的尾部获得。这允许你从跨越两个向量的数据中创建一个新的向量
VEXT能够用来实现两个向量中的滑动窗格,在FIR滤波器中非常有用。对于置换而言,它也能够用来模拟旋转操作,当使用相同操作数的时候。
VTRN 对换一对向量中的8,16,32bit数据。它将以下向量中的数据当做2x2矩阵,然后交换每个矩阵。
使用多个VTRN指令能够交换更大的矩阵,例如,使用三次VTRN指令,一个由16bit数据组成的4x4矩阵能够互相对换数据。
这跟VLD4 VST4的效果一样,当他们需要更少指令时,可以优先与VTRN,尝试使用内存访问的指令。
VZIP and VUZP: Zip and Unzip 压缩与解压缩
VZIP 能够让一对向量的数据( 8, 16 或者 32-bit)进行交错。这个操作与VST2一样的处理效果,因此可以优先使用VST2。
VUZP是VZIP的相反, 将交错的数据恢复原样。这个操作与VLD2类似。
VTBL, VTBX: Table and Table Extend 查表及查表扩展
VTBL 通过向量表及索引向量重组一个新的向量。它是一个字节为单位的查表操作。
向量表有1到4个相邻的D寄存器组成。索引向量中的每一个字节都可以用来索引向量表中的一个字节。根据索引向量上对应索引的位置,被索引的值会被插入到新的向量。
VTBL及VTBX在处理超出范围的索引时不同。如果一个索引超出向量表范围,VTBL在对应的位置上插入0。但是VTBX会保持结果向量中的值不变。
VTBL允许你实现向量的任意置换,只需要设置一个索引寄存器。如果操作是在循环中使用,置换的类型没有变化,你可以在循环外初始化索引寄存器,然后消除设置的开销。
其他
尽管还有其他方式来完成类似置换的操作,例如使用加载和存储指令来操作一个单向量数据,重复访问的内存将使得他们会变得更慢,所以这丫的方式并不推荐。
仔细考虑是否真的需要置换指令是很明智的。然而,当你的算法需要它,置换指令将提供有效的方式把你的数据保存为正确的格式。