Translated from 《NEON Programmer’s Guide》
翻译可能有偏差,描述可能有错误,请以原著为准😁
Chapter 1 : Introduction
Chapter 2 : Compiling NEON Instructions
Chapter 3 : NEON Instruction Set Architecture
Chapter 4 : NEON Intrinsics
Chapter 5 : Optimizing NEON Code
本章介绍了NEON指令集语法
1. Introduction to the NEON instruction syntax
NEON指令语法简介
NEON指令(以及VFP指令)均以字母V开头。指令能够对不同的数据类型进行操作,类型在指令编码中指定,大小以指令的后缀表示,元素的数量由指定的寄存器大小指示。例如下面的指令:
VADD.I8 D0, D1, D2
VADD
表示一个NEON的加操作。I8
后缀表示相加的数据为8位的整形数。D0
,D1
,D2
表示用的是64位寄存器(D0
用来存结果,D1
和D2
用来存操作数)。
这条指令的效果就是,并行地执行8个加法,对象是在64位寄存器中的8个8位通道(lane)。
下一条指令中,输入和输出寄存器的大小是不一样的:
VMULL.S16 Q2, D8, D9
这条指令将4个16位有符号数据相乘,产生4个有符号的32位数据,存到Q2
里面。
VCVT
指令在单精度浮点与32位整数、定点与半精度浮点(如果有实现)之间转换元素。
NEON指令集包括用于将单个或多个值加载或存储到寄存器的指令。此外,还有一些指令可以在多个寄存器和存储器之间传输数据块。在这样的多次传输期间也可以对数据进行交错(interleave)或解交错(de-interleave)。
NEON指令集包括:
- 加法操作。
- 成对的加法,把相邻的向量元素相加。
- 乘法操作,附带增倍(doubling)和饱和(saturating)操作。
- 乘法和累加(accumulate)操作。
- 左移、右移、插入操作。
- 常用逻辑操作(AND,OR,EOR,AND NOT,OR NOT)。
- 选择最小值最大值操作。
- 统计操作,统计leading zeros,signed bits,set bits的数量。
NEON指令集不包括:
- 除法操作(使用
VRECPE
和VRECPS
代替,执行牛顿迭代法(Newton-Raphson iteration))。 - 平方根运算(使用
VRSQRTE
和VRSQRTS
和乘法代替)。
由于NEON指令执行多项操作,因此它们不能将标准ALU(算数逻辑运算单元)标志用在比较指令上。相反,可以比较两个元素,并将比较结果存储在目标寄存器中。如果测试条件为假,则目标寄存器中每个通道的所有位都设置为0,如果测试条件为真,则设置为1。然后可以对该位使用掩码,来控制随后的指令要操作的数据。有按位选择指令可以与这些位掩码一起使用。
一些NEON指令与向量一起作用于标量。与向量一样,标量的大小可以为8位,16位,32位或64位。使用标量的指令可以访问寄存器文件中的任何元素,尽管与乘法指令有所不同。 该指令使用双字向量的索引来指定标量值。乘法指令仅支持16位或32位标量,并且只能访问寄存器文件中的前32个标量(即对于16位标量为D0
-D7
,对于32位标量则为D0
-D15
)。
有许多不同的指令可在寄存器之间或元素之间移动数据。指令也可以交换或复制寄存器,执行逆运算,矩阵转置并提取单个矢量元素。
2. Instruction syntax
指令语法
指令集的通用格式如下:
V{<mod>}<op>{<shape>}{<cond>}{.<dt>} <dest1>{, <dest2>}, <src1>{, <src2>}
<mod>
是修饰符(前置修饰符):
Q
(Saturating)H
(Havling)D
(Doubling)R
(Rounding)
<op>
是要执行的操作,如ADD
、SUB
、MUL
等。
<shape>
是修饰符(后置修饰符):
L
(Long)W
(Wide)N
(Narrow)
<cond>
条件,与IT指令一起使用。
<.dt>
数据类型。
<src1>, <src2>
源寄存器。有一些指令含有立即数。
<dst1>, <dst2>
目的寄存器。目的寄存器可能是好几个寄存器。
2.1 Instruction modifiers
指令修饰符
对于某些指令,您可以指定一个修饰符,该修饰符会更改操作的行为。
修饰符 | 行为 | 例子 | 描述 |
---|---|---|---|
无 | 基本操作 | VADD.I16 Q0, Q1, Q2 | 这样的结果是没有任何修改的 |
Q | Saturation | VQADD.I16 D0, D2, D3 | 如果结果向量中的每个元素超出可表示范围,则将其设置为一个最大值或最小值。范围取决于元素的类型(位数和符号)。如果在任何通道(lane)中发生饱和(saturation),则FPSCR中的粘性QC位置1。 |
H | Halved | VHADD.S16 Q0, Q1, Q4 | 每个元素右移一位(实际上是被截断除以二)。VHADD 可用于计算两个输入的平均值。 |
D | Doubled before saturation | VQDMULL.S16 Q0, D1, D3 | 在以Q15格式乘以数字时,通常需要这样做,在这种情况下,需要额外加倍以使结果转换为正确的格式。 |
R | Rounded | VRSUBHN.I16 D0, Q1, Q3 | 该指令对结果进行舍入,以校正由截断引起的偏差。这等效于在截断之前将结果加0.5。 |
2.2 Instruction shape
指令shape
结果向量和操作数向量具有相同数量的元素。但是,结果中元素的数据类型可能不同于一个或多个操作数中元素的数据类型。因此,结果的寄存器大小也可能与一个或多个操作数的寄存器大小不同。寄存器大小中的这种关系由形状描述。对于某些说明,您可以指定shape
修饰符(后缀)。
- None specified
操作数和结果都是同样的宽度。
例如:VADD.I16 Q0, Q1, Q2
- Long - L (俗称长指令?)
操作数的宽度一样,而在结果(目的寄存器)中,每个元素的宽度是操作数元素的2倍。
例如:VADDL.S16 Q0, D2, D3
(D寄存器是64位,Q寄存器是128位。) - Narrow - N (俗称窄指令?)
操作数的宽度一样,而在结果中,每个元素的宽度是操作数元素的一半。
例如:VADDHN.I16 D0, Q1, Q2
- Wide - W (俗称宽指令?)
结果、操作数是第2操作数宽度的2倍。
例如:VADDW.I16 Q0, Q1, D4
详解:
- Normal常规指令可以操作任何向量类型,产生的结果向量和操作数一样,也是一样的类型和尺寸。
你可以通过在指令助记符中添加一个Q
,来指定正常指令的操作数和结果必须全部为四字(四字,quadword,一般128位)。这样做的话,如果操作数或结果不是全部为四字,汇编器就会报错。 - Long长指令通常对双字(双字,doubleword,一般64位)向量进行运算并产生四字向量。结果元素是操作数元素宽度的2倍。通过在指令后面附加一个
L
来表示长指令。下面图1显示了一个长指令示例,其中输入操作数在操作之前被提升为四字。
- Wide宽指令对双字向量操作数和四字向量操作数进行运算,产生四字向量结果。结果元素和第一个操作数的宽度是第二个操作数元素的宽度的两倍。在指令后面加个
W
表示宽指令。下面图2显示了这一点,在操作之前将输入的双字操作数提升为四字。
- Narrow窄指令对四字向量操作数进行运算,并产生双字向量,结果元素是操作数元素宽度的一半。在指令后面附加一个
N
表示窄指令,图示如下。
3 Specifying data types
指定数据类型
有的NEON指令比其他指令需要知道更多的数据类型。
数据移动 / 替换指令:
- 无需知道如何解析数据,只要知道数据的size即可。
- 如果需要,可以指定更具体的数据类型。
- 不会在操作码中编码,不会在反汇编中显示。
标准加法:
- 必须区分浮点数和整数,但不能区分有符号/无符号整数。
- 如果需要,可指定有符号或者无符号。
饱和(saturating)指令:
- 必须区分符号/无符号,因此必须完全指定数据类型。
操作数的数据类型在指令中指定。通常,只有第二个操作数的数据类型必须被指定。
- 其它的数据类型都可以从指令形状中推断出来,详情见后面的Instruction shape章节。
- 尽管所有的数据类型都能被指定,如果有需要的话。
大多数指令的允许数据类型范围有限。 但是,数据类型的描述是很灵活的:
- 如果描述符指定了
I
,你同样可以使用S
或U
的数据类型。 - 如果仅仅指定了数据的size,你可以指定一种类型(
I
,S
,U
,P
或F
)。 - 如果没有指定数据类型,你可以指定一种数据类型。
F16
数据类型仅在实现半精度体系结构扩展的系统上可用,并且仅适用于转换。
4 Packing and unpacking data
打包和解包数据
对于NEON指令,多个数据元素被打包到单个寄存器中,以允许单个指令同时对寄存器中的所有数据元素进行操作。
打包数据很常见,但有时无法通过NEON指令对打包的数据进行一些处理。因此,必须将数据解压缩到寄存器中。
一种方法是加载打包的数据,通过清除不需要的位来掩盖所需的部分,然后将结果左移或右移。这种方法是可行的,但是效率相对较低。
NEON打包和解包指令简化了打包数据和解压缩数据之间的转换。这使得可以使用字或双字加载有效地加载存储器中的打包数据序列,将其解压缩到单独的寄存器值中,对其进行操作,然后打包回寄存器中,以有效地写出到存储器中。见图:
5 Alignment
对齐
NEON体系结构为NEON数据访问提供了完全的未对齐支持。但是,指令操作码包含了对齐指示,当地址是对齐的,并且指定了对齐指示的时候,能使对齐更快。然而,如果指定了对齐方式但地址未正确对齐,则会发生数据中止。
基地址的指定方式为:[<Rn>:<align>]
注意:指定了对齐提示,然后使用不正确的对齐地址是编程上的错误。
对齐指示可以是:64
、:128
、:256
位其中的一种,这要根据D寄存器的数量而定。
VLD1.8 {D0}, [R1:64]
VLD1.8 {D0,D1}, [R4:128]!
VLD1.8 {D0,D1,D2,D3}, [R7:256], R2
注意:ARM体系指导手册用@
标志来描述,但在源代码里面不推荐用这个。
GNU的gas编译器仍将接受[Rn,:128]
语法(注意冒号之前额外的“,”),但还是首选[Rn:128]
语法。
这适用于Cortex-A8和Cortex-A9处理器。对于Cortex-A8处理器,指定128位或更高的对齐方式可节省一个周期。对于Cortex-A9处理器,指定64位或更高的对齐方式可节省一个周期。
6 Saturation arithmetic
饱和算法
上一节的指令修饰符中,用Q
修饰符来指示饱和运算。这是一种算术形式,其中将数学运算的结果限制为预定的最大值和最小值。如果函数的结果大于最大饱和值,则将其设置为最大值。如果小于最小饱和值,则将其设置为最小值。
例如,如果饱和值的范围为[-10,10]
,则以下操作的结果如下:
- 10 + 5 = 10 10+5=10 10+5=10 (15→10)
- 10 × 5 = 10 10\times5=10 10×5=10 (50→10)
- 5 − 30 = 5-30= 5−30= - 10 10 10 (-25→10)
- 3 + 6 = 9 3+6=9 3+6=9
- 3 × 6 = 10 3\times6=10 3×6=10 (18→10)
- 6 − ( 3 × 5 ) = 6-(3\times5)= 6−(3×5)= - 9 9 9 (文档此处结果是-10,但我觉得应该是-9)
饱和算术常用于数字信号处理之类的应用中,避免像音频信号超出最大范围之类的情况。
7 Floating-point operations
浮点操作
NEON的浮点不是完全符合IEEE-754的。
非规格数(denormals)被刷新为零(flush to zero)。
Rounding固定取最近的数(其实就是四舍五入),但转换操作除外。
仅支持单精度算术(.F32
)。
单独的(标量)浮点指令。
7.1 Floating-point exceptions
浮点异常
在可能导致浮点异常的指令描述符中,有一块subsection列出了异常。如果指令说明中没有“浮点异常”的subsection,则该指令不会导致任何浮点异常。
8 Flush-to-zero mode
刷新到0模式
“刷新到0”(flush-to-zero)模式将非规格(denormalized)数替换为0。这不符合IEEE-754算术,但是在某些情况下,它可以大大提高性能。
在NEON和VFPv3中,“刷新到0”会保留符号位。
在VFPv2中,被刷为
+
0
+0
+0。(不管符号,都刷为正?)
NEON单元经常用“刷新到0”模式。
8.1 Denormals
异常
NEON单元符合IEEE 754-1985标准,但仅支持舍入到最近的舍入(rounding)模式。这是大多数高级语言(例如C和Java)使用的舍入模式。此外,NEON单元始终将非规格值(denormals)视为零。
在浮点算术中,非规格(denormal)是指浮点数有效位的前面是0
。 这意味着数值非常小,尾数的格式为
0.
m
1
m
2
m
3
m
4
.
.
.
m
p
−
1
m
p
0.m_1m_2m_3m_4...m_{p-1}m_p
0.m1m2m3m4...mp−1mp,指数是最小可能指数。
m
m
m是0或1的有效数字,p是精度。
这里的一些概念和术语请参考IEEE745标准,关于浮点数的规范
8.2 The effects of using flush-to-zero mode
“刷新到0”模式的作用
除某些例外,“刷新到0”模式对浮点运算有以下影响:
- 当非规格数(denormalized)作为浮点运算的输入时,它会被当做是0。源寄存器的值不会变。
- 当一个单精度浮点运算的结果在四舍五入(rounding)之前,如果范围在 [ − 2 − 126 , + 2 − 126 ] [-2^{-126},+2^{-126}] [−2−126,+2−126]之间,会被替代成0。
- 当一个双精度浮点运算的结果在四舍五入(rounding)之前,如果范围在 [ − 2 − 1022 , + 2 − 1022 ] [-2^{-1022},+2^{-1022}] [−2−1022,+2−1022]之间,会被替代成0。
每当非规格(denormalized)数被当做操作数,或者结果被刷新到0的时候,会发生一个inexact
异常。在“刷新到0”模式下不会发生Underflow
异常。
8.3 Operations not affected by flush-to-zero mode
不受“刷新到0”模式影响的操作
即使在“刷新到0”模式下,也可以对非规格化(denormalized)的数字执行以下NEON操作,不会把结果刷新为零:
- 复制,绝对值和取反(
VMOV
,VMVN
,V {Q} ABS
和V {Q} NEG
) - 重复(
VDUP
) - 交换(
VSWP
) - 加载和存储(
VLDR
和VSTR
) - 多个加载和多个存储(
VLDM
和VSTM
) - NEON寄存器和ARM通用寄存器之间的传输(
VMOV
)
9 Shift operations
位移操作
本节介绍NEON移位操作,并说明如何使用它们在常用的色深之间转换图像数据。
NEON指令集提供的强大的移位指令范围支持:
- 快速地对向量除以或者乘以 2 n 2^n 2n,具有取整(rounding)和饱和(saturation)。
- 从一个向量按位位移和拷贝到另一个向量。
- 以高精度进行临时计算,以低精度累加结果。
9.1 Shifting vectors
位移向量
NEON移位操作与标量ARM代码中的移位非常相似。移位将向量的每个元素(element)中的位向左或向右移动。每个元素左侧或右侧超出的位将被丢弃,它们不会转移到相邻元素。
移位量可以用指令中的立即数,或附加的移位向量指定。当使用移位向量时,输入向量的每个元素的移位取决于移位向量中相应元素的值。位移向量中的元素被视为有符号值,因此在每个元素的基础上可以向左,向右和零移位。
对有符号元素的向量进行右移操作(由指令所附的类型指示),将对每个元素进行符号扩展。这等效于ARM代码中的算术转换。应用于无符号向量的移位不对扩展符号进行符号化。
9.2 Shifting and inserting
位移和插入
带插入操作的NEON移位提供了一种组合两个向量中的位的方法。例如,向左移动并插入(VSLI
)将源向量的每个元素向左移动。每个元素右侧插入的新位是目标向量中的相应位。如下图所示:
9.3 Shifting and accumulating
位移和累加
有NEON指令可将向量的元素右移,并将结果累加到另一个向量中。这对于在将结果与较低精度的累加器组合之前,以较高的精度进行临时计算的情况很有用。和图6的操作差不多,把“插入”换成了“相加”。
9.4 Instruction modifiers
指令修饰符
每个移位指令可以带有一个或多个修饰符。这些修改器本身不会更改移位操作,但是会调整输入或输出以消除偏置(bias)或饱和(saturate)到某个范围。移位指令有五个修饰符:
- Rounding(舍入),用
R
表示 - Narrow,用
N
表示 - Long,用
L
表示 - Saturating,用
Q
表示 - Unsigned Saturating,用
Q
前缀和U
后缀表示。这类似于饱和(saturating)修饰符,但是当给定有符号或无符号输入时,结果饱和到无符号范围。
某些修饰符的组合是没啥用的,因此没有针对它们的NEON指令。例如,饱和右移(称为VQSHR)就没什么用,因为右移会使结果更小,因此该值不能超出可用范围。
9.4 Table of shifts available
可用位移表
下表列出了所有NEON移位指令,它们是按照前面提到的修饰符排列的。如果仍然不确定修饰语字母的含义,请使用表3,表4和表5选择所需的说明。在表中,Imm
是偏移量的立即数,Reg
是位移向量的寄存器。
Not rounding | rounding | |||||
---|---|---|---|---|---|---|
Default | Long | Narrow | Default | Long | Narrow | |
Left(Imm) | VSHL | VSHLL | ||||
Left(Reg) | VSHL | VRSHL | ||||
Right(Imm) | VSHR | VSHRN | VRSHR | VRSHRN | ||
Left insert(Imm) | VSLI | |||||
Right insert(Imm) | VSRI | |||||
Right accumulate(Imm) | VSRA | VRSRA |
Not rounding | rounding | |||||
---|---|---|---|---|---|---|
Default | Long | Narrow | Default | Long | Narrow | |
Left(Imm) | VQSHL | |||||
Left(Reg) | VQSHL | VQRSHL | ||||
Right(Imm) | VQSHRN | VQRSHRN |
Not rounding | rounding | |||||
---|---|---|---|---|---|---|
Default | Long | Narrow | Default | Long | Narrow | |
Left(Imm) | VQSHLU | |||||
Left(Reg) | ||||||
Right(Imm) | VQSHRUN | VQRSHRUN |
10 Polynomials
多项式
多项式是由任何变量的幂和构成的表达式,其中每个被加数都有一个系数。关于变量 x x x的一个多项式例子: a 2 x 2 + a 1 x + a 0 a_2x^2+a_1x+a_0 a2x2+a1x+a0。
如果数据类型位于 { 0 , 1 } \{0,1\} {0,1}上,则可以使用数据类型P8(8位多项式)和P16(16位多项式)来表示多项式。 { 0 , 1 } \{0,1\} {0,1}上的多项式是系数为0或1的多项式。对于系数不是0或1的多项式,不能使用NEON多项式算法。
如果 f f f是一个这样的多项式,使用P8数据类型中的8位, f f f可以表示系数为 { a 7 , a 6 , a 5 , a 4 , a 3 , a 2 , a 1 , a 0 } \{a_7,a_6,a_5,a_4,a_3,a_2,a_1,a_0\} {a7,a6,a5,a4,a3,a2,a1,a0}的序列,其中 a n a_n an为0或1。或者,您可以使用P16数据类型将 f f f表示为序列 { a 15 , a 14 , a 13 , a 12 , a 11 , a 10 , a 9 , a 8 , a 7 , a 6 , a 5 , a 4 , a 3 , a 2 , a 1 , a 0 } \{a_{15},a_{14},a_{13},a_{12},a_{11},a_{10},a_9,a_8,a_7,a_6,a_5,a_4,a_3,a_2,a_1,a_0\} {a15,a14,a13,a12,a11,a10,a9,a8,a7,a6,a5,a4,a3,a2,a1,a0}。
因此,NEON寄存器D或Q 中的每个8位或16位通道(lane)都包含多项式的系数序列,例如 f f f。
注意: 您可以将1s和0s的多项式序列可视化为8位或16位无符号值。但是,多项式算法与常规算法不同,并且会产生不同的结果。
10.1 Polynomial arithmetic over {0,1}
{0,1}上的多项式算术
多项式算法在实现某些密码学或数据完整性算法时很有用。
多项式系数0和1使用布尔算术规则进行操作:
- 0 + 0 = 1 + 1 = 0 0+0=1+1=0 0+0=1+1=0
- 0 + 1 = 1 + 0 = 1 0+1=1+0=1 0+1=1+0=1
- 0 ∗ 0 = 0 ∗ 1 = 1 ∗ 0 = 0 0*0=0*1=1*0=0 0∗0=0∗1=1∗0=0
- 1 ∗ 1 = 1 1*1=1 1∗1=1
对 { 0 , 1 } \{0,1\} {0,1}上的两个多项式相加,相当于与按位异或(OR)。因此,多项式相加会导致与常规相加不同的值。
对 { 0 , 1 } \{0,1\} {0,1}上的两个多项式相乘,首先确定按常规乘法完成的部分乘积,然后对部分乘积进行异或运算,而不是按常规方式进行相加。多项式乘法的结果与常规乘法不同,因为它需要部分乘积的多项式加法。
多项式类型可以帮助那些必须使用2的幂的有限域或简单多项式的任何事物。普通的ARM整数代码通常用查表的方法来进行有限域的算术运行,但大型的查找表无法向量化。多项式运算很难从其他运算中合成出来,因此具有基本的乘法运算(加法运算为按位异或)是有用的,从中可以合成较大的乘法或其他运算。
多项式算法包括:
- 纠错,例如Reed Solomon编码。
- 循环冗余校验(Cycle Redundancy Checking,CRC)。
- 椭圆曲线密码学
10.2 NEON instructions that can perform polynomial arithmetic
可以进行多项式计算的NEON指令
有一些NEON指令可对多项式数据类型P8和P16进行操作。乘法指令是唯一的指令,能够改变多项式数据类型的行为,把它从常规的乘法转为多项式乘法。当使用多项式数据类型时,VMUL
和VMULL
是仅有的两个可以执行多项式乘法的指令。
VADD
指令执行常规加法,不能用于执行多项式加法。多项式加法与按位异或运算完全相同,因此对于多项式加法,必须使用VEOR
指令。
但是,VEOR
内部函数(instrinsic)不接受多项式数据类型P8和P16。因此,在使用内部函数(instrinsic)时,必须将数据类型从P8或P16重新解释为VEOR
内部函数(instrinsic)接受的数据类型之一。 为此,请使用NEON内部函数(instrinsic)vreinterpret
。
10.3 Difference between polynomial multiply and conventional multiply
多项式乘法与常规乘法之间的区别
多项式加法表现为按位异或(无进位加法),而不是常规加法(带有进位的加法)。即使在单个位上的多项式乘法与在单个位上的常规乘法相同,这也会导致多项式乘法的差异。部分结果必须是按位异或运算,而不是常规地相加。在某些系统中,这称为无进位乘法。
下面的例子显示了常规算术中3乘3的乘积。乘法以二进制显示,以突出显示差异。 乘法的结果是b01001
,也就是十进制的9,这是常规乘法所期望的。 不会用latex打竖式公式,直接截图了:
下一个例子显示多项式算术中3与3的乘积。乘法以二进制显示,以突出显示差异。当对位值进行运算时,多项式乘法与常规乘法相同,对中间过程得到的乘积的多项式加法是异或。多项式相乘的结果为b00101
,等于5。
11 Instructions to permute vectors
置换向量的指令
当可用的算术指令与寄存器中的数据格式不匹配时,在向量处理中有时需要进行置换(permutation)或更改向量中元素的顺序。他们从一个寄存器或跨多个寄存器中选择单个元素,以形成一个新向量,该向量与处理器提供的NEON指令更好地匹配。
置换(permutation)指令与移动(move)指令相似,因为它们用于准备或重新排列数据,而不是修改数据的值。好的算法设计可以无需重新排列数据。因此,请考虑在您的代码中是否需要置换指令。降低对置换和移动指令的需求通常是一种更好的方式。
11.1 Alternatives
备用方案
下面有一些方法可以避免不必要的置换(permutation):
- 重新排列您的输入数据。 通常,以更合适的格式存储数据无需花费任何开销,也不需要在加载和存储时对它进行置换。但是,在更改数据结构之前,请考虑数据局部性及其对缓存性能的影响。
- 重新设计算法。 可以用数量上差不多的处理步骤,但以不同格式处理数据的不同算法。
- 修改前面的处理步骤。 在较早的处理阶段进行很小的更改,即调整将数据存储到内存的方式,可以减少或消除对置换操作的需求。
- 使用交错(interleaving)加载和存储。 加载和存储指令具有交错和解交错的能力。如果这不能完全消除置换的需要,则可能会减少所需的其他指令数量。
- 结合方法。 与其他置换指令相比,结合上面几种方法可能仍然更有效。
如果你考虑了上述所有方法,还是不能得到合适的数据格式,那就不得不用用置换指令了。
11.2 Instructions
指令
本节内容请参考我的另外一篇文章 ,有配图。
置换指令有很多,从简单的翻转到随意地重构向量。简单的置换(permutation)可以在一个指令周期内搞定,而更复杂的操作则需要多个周期,并且可能需要设置其他寄存器。 与往常一样,请查看处理器的《技术参考手册》以获取性能详细信息。
VMOV and VSWP: Move and Swap
VMOV
和VSWP
是最简单的置换(permute)指令,可以将整个寄存器的内容复制到另一个寄存器中,或者交换一对寄存器中的值。
尽管您可能不会将它们视为置换指令,但是可以利用两个D寄存器来交换一个Q寄存器的高低位。 例如,VSWP d0,d1
交换q0
的最高和最低有效64位。
VREV: Reverse
VREV
反转向量中8位,16位或32位元素的顺序,有三种变体:
VREV16
反转构成向量的16位元素的每对8位子元素。
VREV32
反转构成向量的32位元素的每对16位子元素,或4个8位子元素。
VREV64
反转构成向量的64位元素的每对32位子元素,或4个16位子元素,或8个8位子元素。
VREV
反转数据的字节序,通常用于重新排列颜色分量或交换音频样本的通道。
VEXT: Extract
VEXT
从一对现有向量中提取字节组成新的向量。新向量中的字节从第一个操作数的顶部到第二个操作数的底部。这使您可以生成一个新向量,其中包含跨越一对现有向量的元素。
VEXT
可以用来在两个向量上实现滑动窗口,在FIR滤波器上非常有用。对于置换(permutation),当两个输入操作数使用相同的向量时,也可以用来模拟按字节旋转的操作。
VTRN: Transpose
VTRN
在一对向量之间转置8、16或32位元素。 它将向量的元素视为2x2矩阵,并转置每个矩阵。
使用多个VTRN
指令来转置较大的矩阵。例如,由16位元素组成的4x4矩阵可以使用三条VTRN
指令进行转置。
在加载向量之后或存储向量之前,这与VLD4
和VST4
执行的操作相同。 由于它们需要较少的指令,因此请尽可能尝试使用这些结构化的内存访问功能,而不要使用一系列VTRN
指令。
VZIP and VUZP: Zip and Unzip
VZIP
交错(interleaves)一对向量的8、16或32位元素。在存储数据之前,该操作与VST2
执行的操作相同。所以如果需要在写回内存之前立即压缩数据,请使用VST2
,而非VZIP
。
VUZP
是VZIP
的反向操作,反交错(deinterleaving)一对向量的8位,16位或32位元素。在从内存加载数据之后,该操作与VLD2
所做操作相同。
VTBL, VTBX: Table and Table Extend
VTBL
从向量表和索引向量构造一个新的向量。这是一个逐字节的表查找操作。
该表由1到4个相邻的D寄存器组成。 索引向量中的每个字节都用于索引向量表中的一个字节。 将索引值插入到结果向量中与索引向量中原始索引位置相对应的位置。
VTBL
和VTBX
在处理超出范围索引的方式上有所不同。如果一个索引超过了表的长度,VTBL
会在结果向量的相应位置插入0,但是VTBX
保持结果向量中的值不变(如上图,d2
原来是多少就是多少)。
如果将单个源向量用作表,则VTBL
允许您实现向量的任意排列,但要以设置索引寄存器为代价。 如果是在一个循环里面使用该操作,并且排列的类型不变,则可以在循环的外部初始化索引寄存器,并消除设置开销。