什么是SVE
NEON 指令集是ARM64 架构的单指令多数据流(SIMD )的标准实现。SVE( 可扩展矢量指令 Scalable Vector Extension) 是针对高性能计算(HPC )和机器学习等领域开发的一套全新的矢量指令集,它是下一代SIMD 指令集实现,而不是NEON 指令集的简单扩展。SVE 指令集中有很多概念与NEON 指令集类似,例如矢量、通道、数据元素等。SVE 指令集也提出了一个全新的概念: 可变矢量长度编程模型(VectorLength Agnostic ,VLA) 。
传统的SIMD 指令集采用固定大小的向量寄存器,例如NEON 指令集采用固定的128 位长度的矢量寄存器。而支持VLA 编程模型的SVE 指令集则支持可变长度的矢量寄存器。这样允许芯片设计者根据负载和成本来选择一个合适的矢量长度。SVE 指令集的矢量寄存器的长度最小支持128 位,最大可以支持2048 位,以128 位为增量。SVE 设计确保同一个应用程序可以在支持不同矢量长度的SVE 指令机器上运行,而不需要重新编译代码,这是VLA 编程模型的精髓。
SVE 指令集是在A64指令集的基础上新增的一组指令集,而SVE2是在ARMv9架构上发布的,它是SVE指令集的一个超集和扩充。
SVE 指令集包含了几百条指令,它们可以分成如下几大类。
Ø 加载存储指令以及预取指令
Ø 向量移动指令
Ø 整数运算指令
Ø 位操作指令
Ø 浮点数运算指令
Ø 预测操作指令
Ø 数据元素操作指令
SVE2 指令集在SVE指令集的基础进一步扩充和完善,新增了部分指令和扩展。本节不对每条指令做详细的介绍,有兴趣的读者可以阅读ARMv9指令集文档:《Arm A64 Instruction SetArchitecture,Armv9, for Armv9-A architectureprofile》。
什么是矢量计算?
SIMD ,全称Single Instruction Multiple Data ,一条指令操作多个数据,提供小数据并行处理能力。ARM 从ARMv7 架构开始加入NEON 指令集扩展,矢量化并行计算,用于图像处理、音视频处理、视频编解码等场景。
SISD (Single Instruction Single Data )指的是单指令单数据。大多数ARM64 指令是单指令单数据(SISD) 。每条指令在单个数据源上执行其指定的操作,所以,处理多个数据项需要多个指令。例如,要执行4 个加法操作,需要4 条指令从4 对寄存器进行加法运算。
ADD w0, w0, w5
ADD w1, w1, w6
ADD w2, w2, w7
ADD w3, w3, w8
如果数据元素比较小,例如当将8 位值相加时,需要将每个8 位值加载到一个单独的64 位寄存器中。由于处理器、寄存器和数据路径都是为64 位计算而设计的,所以在小数据大小上执行大量单独的操作不能有效地使用机器资源。
SIMD 指的是单指令多数据流,它对多个数据元素同时执行相同的操作。这些数据元素被打包成一个更大的寄存器中的独立通道(Lanes )。例如,ADD 指令将32 位数据元素加在一起。这些值被打包到两对128 位寄存器(分别是V8 和V9 )中的单独通道中。然后将第一源寄存器中的每个通道添加到第二源寄存器中的相应通道,然后将其存储在目标寄存器(V10 )中的同一通道中。
如 图1 所示,ADD 指令会并行做4 个加法运算,它们分别位于处理器内部的4 个计算通道并且是相互独立的,任何一个通道发生了溢出或者进位都不会影响其他通道。
图1 SIMD 的 ADD 指令
在图 1 中,一个128 位的矢量寄存器Vn 可以同时存储4 个32 位的数据Sn ,另外它还可以存储2 个64 位数据Dn 、8 个16 位数据Hn 或者16 个8 位数据Bn 。
SIMD 非常适合图像处理场景。图像的数据常用的数据类型是RGB565, RGBA8888,YUV422 等格式,这些格式的数据特点是一个像素点的一个分量(A 、R 、G 以及B 分量)使用8 位数据表示。如果使用传统的处理器做计算,虽然处理器的寄存器是32 位或是64 位的,处理这些数据确只能使用寄存器的低8位,有点浪费。如果把64 位寄存器拆成8 个8 位数据通道就能同时完成8 个操作,计算效率提升了8 倍。
总之,SISD 和SIMD 的区别 如图 2 所示。
图2 SISD与SIMD
矢量与通道
在SIMD 指令中常常使用矢量数据格式(Vectorformats )。矢量被划分为多个通道(lanes ),每个通道包含一个矢量元素(vector elements )。 如图3 所示 ,一个Vn 矢量寄存器可以分成8 个16 位数据,如通道0 、通道1 等。
图3 矢量寄存器与通道
通道可以由多种不同的数据类型组成,比如128 位的数据类型用Vn 来表示,64 位的数据类型用Dn 来表示,32 位的数据类型用Sn 来表示,16 位的数据类型用Hn 来表示,8 位的数据类型用Bn 来表示 , 如图4 所示 。
图4 数据类型
在矢量 指令集(NEON/SVE)中,指令通常可以分成两大类,一类是矢量(vector )运算指令,另一类是标量(scalar )运算指令。矢量运算指的是对矢量寄存器中所有通道的数据都同时进行运算,而标量运算指的是只对矢量寄存器中某个通道的数据进行运算。
SVE寄存器组
SVE 指令集提供了一组全新的寄存器。
Ø 32 个全新的可变长矢量寄存器Z0~ Z31 。
Ø 16 个预测寄存器(predicateregister )P0 ~ P15 。
Ø 首次错误预测寄存器(First Fault predicateRegister ,FFR )
Ø SVE 控制寄存器ZCR_Elx
(1) 可变长矢量寄存器Z
Z 寄存器是数据寄存器,长度是可变长的。它的长度是128 的倍数,最高可达2048 位。Z 寄存器中的数据可以用来存储为8 位、16 位、32 位、64 位或128 位数据元素, 如图5 所示 。每个Z 寄存器的低128 位与对应的NEON 寄存器复用。
图5 z寄存器
(2) 预测寄存器P
P
寄存器为Z
寄存器中的每个字节保留一个位,也就是说,P
寄存器总是Z
寄存器的1/8
大小。预测指令(Predicatedinstruction
)使用P
寄存器来确定要处理哪些向量元素(通道数据)。P
寄存器中的每个比特位指定Z
寄存器中相应的字节是处于活跃状态还是非活跃状态。
当数据元素为8 位宽(Bn )时,P 寄存器可以使用1 个比特位来表示其活跃状态,这个比特位为1 表示活跃,为0 表示不活跃。依次类推,当数据元素为128 位宽(Vn )时,P 寄存器预留了8 个比特位来表示Z 寄存器中对应的数据元素的活跃状态,不过只使用了最低1 位即可表示其活跃状态,其他比特位保留。
假设矢量寄存器的长度为256 位,矢量寄存器分成了8 个通道,每个通道存储32 位宽的数据。如果在SVE 指令中想同时操作这8 个通道的数据,那么需要使用一个P 寄存器来表示这8 个数据通道的状态。 如图6 所示 ,Pn 寄存器也分成8 个组,每个组由4 个比特位组成,每个组只使用最低的比特位来表示Zn 寄存器中对应的数据通道(32 位宽)的活跃状态。例如,Pn 寄存器的Bit[3:0] 表示Zn 寄存器的通道0 ,Pn 寄存器的Bit[7:4] 表示Zn 寄存器的通道1 ,依次类推。
图6 预测寄存器
(3) FFR 寄存器
FFR 寄存器的大小与格式和预测寄存器P 相同。FFR 寄存器用于第一异常预测加载指令(First Fault PredicteLoad Instruction ),例如LDRFF 指令。在使用第一异常预测加载指来加载矢量元素时,FFR 寄存器会及时更新每个数据元素的加载状态,是成功还是失败
(4) ECR_ELx 寄存器
系统软件可以通过ECR_ELx 寄存器中的LEN 字段来设置矢量寄存器的长度。不过,设置的长度不能超过硬件实现的长度。
SVE指令语法
SVE 指令的语法与NEON 指令有很大不同。SVE 指令格式由操作代码、目标寄存器、P 寄存器和输入操作符等组成。下面举几个例子来介绍。
【例 】
下面是一条LD1D 指令的格式。
LD1D {
其中:
Ø Zt 表示矢量寄存器,可以使用Z0~Z31 。
Ø D 表示矢量寄存器中通道的数据类型。
Ø Pg 表示预测操作数(predicateoperand ),可以使用P0~P15 。
Ø
Ø Xn/SP :表示源操作数的基地址,Xn 为通用寄存器,SP 为栈指针寄存器。
Ø Xm :表示第二个源操作数寄存器。
【例 2 】
下面是一条ADD 指令的格式。
ADD
其中:
Ø Zdn 表示第一个源矢量寄存器或者目标矢量寄存器。
Ø Pg 表示预测寄存器,可以使用P0~P15 。
Ø
Ø Zm 表示第二个源矢量寄存器。
Ø T 表示矢量寄存器中通道的数据类型。
ARM64视频课