这篇指南是对Arm AArch64架构可伸缩向量扩展(SVE)的一篇简介。这在篇指南中,你可以学到SVE的主要特性,SVE的应用领域以及SVE和NEON的区别。我们也会描述对一个支持SVE的目标平台如何开发SVE程序。
这篇文章假设你已经熟悉以下概念:
【译者】如果你对这些概念不熟悉,可以读读我翻译的另外两篇 ARM SIMD NEON 简介 (翻译自 Introducing NEON Development Article) 和 ARM Cortex-A 系列编程指南之ARMv8-A【AArch64浮点与NEOE】
随着 Neon 架构扩展(其指令集具有固定的 128 位向量长度)的开发,Arm 设计了可扩展向量扩展 (SVE) 作为 AArch64 的下一代 SIMD 扩展。SVE引入可扩展概念, 允许灵活的向量长度实现,使其能够在现在或将来的多应用场景下实现伸缩,允许CPU设计者自由选择向量的长度来实现。矢量长度可以从最小 128 位到最大 2048 位不等,以 128 位为增量。SVE的设计保证同样的应用程序可以在支持SVE的不同实现上执行,而无需重新编译代码。 SVE 提高了架构对高性能计算 (HPC) 和机器学习 (ML) 应用程序的适用性,这些应用程序需要大量数据处理。
SVE 引入以下关键特性:
当我们处理大数据集时,这些特性有助于向量化和优化循环。
SVE 不是 Neon 指令集的扩展,也不是替代品。 SVE 经过重新设计,可以为 HPC 和 ML 提供更好的数据并行性。
这部分介绍SVE的基础架构特性。
SVE基于一组可扩展的向量。SVE添加了以下寄存器:
Z0-Z31
P0-P15
ZCR_Elx
让我们依次了解一下这些寄存器。
Z0-Z31
可扩展的向量寄存器Z0-Z31
在微架构上可以实现为128到2048位。低128位同时也被128位定长的NeonV0-V31
寄存器共享。
下图展示了可扩展的向量寄存器Z0-Z31
:
此可扩展向量:
P0-P15
为了控制操作中涉及哪些活动元素,谓词寄存器在许多 SVE 指令中用作掩码,这也为向量操作提供了灵活性。下图展示了可扩展谓词寄存器P0-P15
:
谓词寄存器通常被用作对数据操作的bit mask:
Zx
(可扩展向量寄存器)的 1 / 8 1/8 1/8P0-P7
是控制加载、存储和算术的谓词。P8-P15
是用于循环管理的额外的谓词。谓词寄存器也可以用作各种 SVE 指令中的操作数。
在实现的最大向量长度内,还可以通过 ZCR_Elx
寄存器为每个异常级别配置向量的长度。长度实现和配置需要满足 AArch64 SVE Supplement 中的最低要求,以便满足以下其中任何一个条件:
特权异常级别可以使用可伸缩向量控制寄存器 ZCR_El1
、ZCR_El2
和 ZCR_El3
的 LEN
字段来限制该异常级别和较低特权异常级别的向量长度:
可扩展矢量系统控制寄存器指示 SVE 实现特性:
SVE 汇编语法格式由操作码、目的寄存器、谓词寄存器(如果指令支持谓词掩码)和输入运算符组成。 以下指令示例显示了此格式的详细信息。
示例1
LDFF1D {<Zt>.D},
其中,
是向量,Z0-Z31
.D
和.D
指定目标和操作数向量的元素类型,不需要指定元素的数量
是谓词,P0-P15
/Z
是归零断言(zeroing predication)
指定gather-load地址模式的偏移量示例2
ADD <Zdn>.
其中,
M
是合并谓词
既是目标寄存器,也是输入运算符之一。 为方便起见,指令语法在两个地方都显示了
。 在汇编编码中,为了简化,它们被编码一次。示例3
ORRS
S
是对谓词条件标志 NZCV
的新解释
控制谓词在示例操作中充当“位掩码”。SVE引入了以下重要的架构特性:
单通道谓词(per-lane predication)
为了允许对选定元素进行灵活操作,SVE 引入了 16 个控制谓词寄存器 P0-P15
,以指示向量的活动通道上的有效操作。 例如:
ADD Z0.D, P0/M, Z0.D, Z1.D
活动元素 Z0
和 Z1
相加并将结果放入 Z0
。P0
指示操作数的哪些元素是活动的和非活动的。 P0
后面的“M”表示Merging,表示将非活动元素合并,因此 Z0
的非活动元素在ADD
操作后将保持其原始值。 如果在 P0
之后是“Z”,即归零,则目标寄存器的非活动元素将在操作后归零。
如果谓词规范是“/Z”,则操作将对目标向量的相应元素的结果进行归零,其中谓词元素为零。 例如:
CPY Z0.B, P0/Z, #0xFF
将有符号整数 0xFF
复制到 Z0
中,其中 Z0.B
的非活动元素将设置为零。
指令有谓词选项。 此外,并非所有谓词操作都具有合并和归零选项。 您必须参考 AArch64 SVE Supplement 以了解每条指令的规范细节。
聚集加载和分散存储(gather-load and scatter-store)
SVE 中的地址模式允许将向量用作 Gather-load 和 Scatter-store 指令中的基地址和偏移量,从而实现非连续内存位置。 例如:
LD1SB Z0.S, P0/Z, [Z1.S] //从由 32 位向量基 Z1 生成的内存地址聚集加载有符号字节负载到 Z0 活动的 32 位元素。
LD1SB Z0.D, P0/Z, [X0, Z1.D] //从由 64 位标量基 X0 加上 Z1.D 中的向量索引生成的内存地址中聚集加载有符号字节负载到 Z0 活动的元素。
下面的例子展示了 LD1SB Z0.S, P0/Z, [Z1.S]
的加载操作,其中 P0
Z1
包含分散的地址。 加载后,每个 Z0.S
的低位字节都会使用从分散的内存位置获取的数据进行更新。
谓词驱动的循环控制和管理
作为 SVE 的一个关键特性,谓词不仅可以灵活地控制向量运算的各个元素,还可以实现谓词驱动的循环控制。 谓词驱动的循环控制和管理使循环控制高效灵活。 此功能通过在谓词寄存器中注册活动和非活动元素索引,消除了处理部分向量的额外循环头和尾的开销。 谓词驱动的循环控制和管理意味着,在接下来的循环迭代中,只有活动元素执行预期的选项。 例如:
WHILEL0 P0.S, x8, x9
B.FIRST Loop_start
在 P0 中生成一个谓词,从最低编号的元素开始为真,当第一个无符号标量 X8 操作数的值低于第二个标量操作数 X9,此后为假,直到最高编号的元素。
B.FIRST(相当于B.MI)或B.NFRST(相当于B.PL)常用于一个循环根据上述指令测试P0的第一个元素是真还是假的结果作为结束或继续条件进行分支。
用于软件控制的投机的向量分区
投机性加载会对传统向量的内存读取造成挑战,如果在读取过程中某些元素发生故障,则很难逆向加载操作并跟踪哪些元素加载失败。 Neon 不允许投机性负载。 为了允许对向量进行投机性加载,SVE 引入了第一个故障向量加载指令,例如 LDRFF
。 为了允许向量访问进入无效页面,SVE 还引入了 First-Fault 谓词寄存器 (FFR)。当使用第一个故障向量加载指令加载到 SVE 向量时,FFR 寄存器会更新每个元素的加载成功或失败结果。 当发生加载故障时,FFR立即注册对应的元素,将对应的其余元素注册为0或false,不触发异常。 通常,RDFFR
指令用于读取 FFR 状态。 当第一个元素为假时,RDFFR
指令完成迭代。 如果第一个元素为真,RDFFR
指令将继续迭代。 FFR 的长度与谓词向量相同,其值可以用 SETFFR
指令初始化。 以下示例使用 LDFF1D
从内存中读取,FFR 相应更新:
LDFF1D Z0.D, P0/Z, [Z1.D, #0]
从向量基数 Z1
加 0 生成的内存地址聚集加载具有双字的第一个故障行为的负载到 Z0
的活动元素。非活动元素将不会读取设备内存或信号故障,并在目标向量中设置为零。 从有效内存成功加载将对 FFR 中的元素设置为 true。 第一个故障负载会将相应元素和 FFR 中的其余元素设置为 false 或 0。
扩展的浮点和水平归约
为了允许向量中的高效归约操作,并满足对精度的不同要求,SVE 增强了浮点和水平归约操作。 指令可能具有按顺序(从低到高)或基于树(成对)的浮点归约排序,其中操作排序可能会导致不同的舍入结果。 这些操作权衡可重复性和性能。 例如:
FADDA D0, P0/M, D1, Z2.D
浮点加法从向量源的低位元素到高位元素的严格顺序归约,将结果累加到 SIMD&FP 标量寄存器中。 示例指令将 D1
和 Z2.D
的所有活动元素相加,并将结果放入标量寄存器 D0
。 向量元素严格按照从低到高的顺序处理,标量源 D1
提供初始值。 源向量中的非活动元素被忽略。 而 FADDV
将执行递归的成对归约,并将结果放入标量寄存器中。
本节介绍支持 SVE 应用程序开发的软件工具和库。 本节还介绍了如何为支持 SVE 的目标开发应用程序,并在支持 SVE 的硬件上运行它。本节还将描述如何在基于 Armv8-A 的硬件的 SVE 仿真环境下运行应用程序。
要构建 SVE 应用程序,您必须选择支持 SVE 功能的编译器,例如:
有多种方式可以写或生成SVE代码。在这部分指南,我们给出四种SVE编程的方法:
您可以将 SVE 指令编写为 C/C++ 代码中的内联汇编或汇编源代码中的完整函数。 例如:
.globl subtract_arrays // -- Begin function
.p2align 2
.type subtract_arrays,@function
subtract_arrays: // @subtract_arrays
.cfi_startproc
// %bb.0:
orr w9, wzr, #0x400
mov x8, xzr
whilelo p0.s, xzr, x9
.LBB0_1: // =>This Inner Loop Header: Depth=1
ld1w { z0.s }, p0/z, [x1, x8, lsl #2]
ld1w { z1.s }, p0/z, [x2, x8, lsl #2]
sub z0.s, z0.s, z1.s
st1w { z0.s }, p0, [x0, x8, lsl #2]
incw x8
whilelo p0.s, x8, x9
b.mi .LBB0_1
// %bb.2:
ret
.Lfunc_end0:
.size subtract_arrays, .Lfunc_end0-subtract_arrays
.cfi_endproc T
如果您要混合使用高级语言和汇编语言编写的函数,则必须熟悉针对 SVE 更新的应用程序二进制接口 (ABI) 标准。 Arm 体系结构的过程调用标准 (AAPCS) 指定了数据类型和寄存器分配,并且与汇编编程最相关。 AAPCS 要求:
Z0-Z7
和 P0-P3
用于传递可缩放矢量参数和结果。Z8-Z15
和 P4-P15
是被调用者保存的。Z16-Z31
)都可以被被调用函数破坏,调用函数负责在需要时备份和恢复它们。SVE 内在函数是编译器支持的函数,可以用相应的指令替换。 程序员可以直接调用C、C++等高级语言的指令函数。 SVE 的 ACLE(Arm C 语言扩展)定义了哪些 SVE 指令函数可用、它们的参数以及它们的作用。 支持 ACLE 的编译器可以在编译期间用映射的 SVE 指令替换内在函数。 要使用 ACLE 内在函数,您必须包含头文件“arm_sve.h”,其中包含可在 C/C++ 中使用的向量类型和指令函数(针对 SVE)的列表。 每种数据类型都描述了向量中元素的大小和数据类型:
svint8_t svuint8_t
svint16_t svuint16_t svfloat16_t
svint32_t svuint32_t svfloat32_t
svint64_t svuint64_t svfloat64_t
例如,svint64_t 表示 64 位有符号整数的向量,svfloat16_t 表示半精度浮点数的向量。
以下示例 C 代码已使用 SVE 内在函数手动优化:
//intrinsic_example.c
#include
svuint64_t uaddlb_array(svuint32_t Zs1, svuint32_t Zs2)
{
// widening add of even elements
svuint64_t result = svaddlb(Zs1, Zs2);
return result;
}
包含 arm_sve.h
的源代码可以使用 SVE 向量类型,就像数据类型可用于变量声明和函数参数一样。 要使用 Arm C/C++ 编译器编译代码,并以支持 SVE 的 Armv8-A 架构为目标,请使用:
armclang -O3 -S -march=armv8-a+sve -o intrinsic_example.s intrinsic_example.c
此命令生成以下汇编代码
//instrinsic_example.s
uaddlb_array: // @uaddlb_array
.cfi_startproc
// %bb.0:
uaddlb z0.d, z0.s, z1.s
ret
此示例使用Arm Compiler for Linux 20.0版本
C/C++/Fortran 编译器,例如用于 Linux 的原生 Arm 编译器 和用于 Arm 平台的 GNU 编译器,支持使用 SVE 指令对 C、C++ 和 Fortran 循环进行向量化。 要生成 SVE 代码,请选择适当的编译器选项。 例如,当 armclang
使用 -march=armv8-a+sve
选项时,armclang
还使用默认选项 -fvectorize
和 -O2
。 如果要使用支持 SVE 的库版本,请将 -march=armv8-a+sve
与 -armpl=sve
结合使用。 有关编译器优化选项的更多信息,请参阅编译器开发人员和参考指南,或编译器手册页。
使用针对 SVE 高度优化的库,例如 Arm 性能库和 Arm 计算库。 Arm 性能库包含针对 BLAS、LAPACK、FFT、稀疏线性代数和 libamath 优化的数学函数的高度优化实现。 为了能够链接任何 Arm 性能库函数,您必须安装 Arm Allinea Studio 并在代码中包含 armpl.h。要使用 Arm Compiler for Linux 和 Arm 性能库构建应用程序,您必须在命令行上指定 -armpl=
。 如果使用 GNU 工具,则必须在链接器命令行中使用-L
包含 Arm Performance Libraries 安装路径,并指定 GNU 等效于 Arm Compiler for Linux armpl=
选项,也就是-larmpl_lp64
。 有关更多信息,请参阅 Arm 性能库入门指南。
如果您无权访问 SVE 硬件,则可以使用模型或仿真器来运行您的代码。 有几个模型和模拟器可供选择:
以下是与本指南内容相关的一些资源: