Page 16 - 《软件学报》2021年第8期
P. 16

2298                                   Journal of Software  软件学报 Vol.32, No.8,  August 2021

                    HPL 算法中传递给 BLAS 函数的参数会有固定值的情况,可以通过适当的指令替换来精简指令,举例如下.
                    (1)  对于 Level-3 级函数 DGEMM
                    系数 Alpha 固定为–1.0,Beta 固定为 1.0.此时的 DGEMM 计算公式由 C:=α·AB+β·C 变成了 C:=C−AB 可以
                 在 packing B(见第 2.3 节)的过程中使用 xorpd 指令进行符号位翻转,精简了对系数 Alpha 的乘法操作.矩阵 C 不
                 变,精简了对系数 Beta 的乘法操作.精简指令后,原来需要 4 个 core cycle 的乘法操作和 5 个 core cycle 的乘加指
                 令只需 3 个 core cycle 的加法指令即可完成.在矩阵规模较大,最内层 kernel 被循环多次的情况下,指令时钟周期
                 的减少可以优化整体性能.
                    (2)  对于 Level-2 级函数 DGEMV
                    转置参数 TransA 固定为 NoTrans,可以省略对矩阵 A 进行转置操作的代码,提高 CPU 流水线分支预测的准
                 确率,避免分支预测失败的惩罚.Alpha 固定为–1.0,Beta 固定为 1.0,则可以在内层 kernel 中使用一条
                 VFNMADD231PD 指令取代 VMULPD 和 VFMADD231PD 两条指令.
                    (3)  对于 Level-1 级函数 DSCAL
                    IncX 固定为 1,可以省去对数据成员间隔较大时所需的软件预取操作的指令(见第 2.4 节).
                    异构 HPL 传递给 BLAS 接口的参数还有其他的固定值情况存在,这些固定值的存在可以起到精简指令的
                 作用,为 BLAS 库提供了优化空间.
                 3.2   循环展开
                    循环展开是编译器常用的优化方式之一,可以减少分支预测失败带来的开销,提高指令级的并行度.但高级
                 语言通过编译器循环展开后的汇编代码在 BLAS 这种访存密集型的程序特征下,往往性能表现较差.因此 BLAS
                 的核心循环代码通常采用手写汇编的方式进行展开.
                    手写 BLAS 核心汇编代码,不仅需要考虑底层 CPU 架构层面的架构寄存器的数量和复用方式,而且需要考
                 虑底层 CPU 的微架构特点,比如浮点部件的宽度、存储层级的大小和层级间的延迟等.这些 CPU 特性共同决定
                 了手写汇编循环展开的程度,也就是“循环展开因子”.本文第 2.1 节中提到的 5 个参数 n c 、k c 、m c 、n r 和 m r ,其中
                 内核参数 n r 和 m r 与汇编代码循环展开直接相关,而矩阵分块参数 n c ,k c 和 m c 则与 CPU 存储层级特性直接相关.
                 针对具体平台体系结构可以理论计算出这 5 个参数的大致取值                     [30] ,但实际代码测试中发现,理论数据往往并不
                 是最优设置,X86 CPU 有 16 个 128 位的向量寄存器 XMM 0 ~XMM 15 ,内核参数 n r ,m r 取值的首要策略是最大化利
                 用架构寄存器,因此综合考虑,n r 和 m r 分别取值为 6 和 4.同时矩阵参数[n c ,k c ,m c ]依据第 2.2 节中的自动调优技术
                 同样获得了比理论值更好的参数设置.
                    如图 6 所示,通用寄存器 RAX 用于索引 A c 的 micro-panel 中的数据,不断获取一个双精度数据然后复制成
                 两份填充到 XMM0 中;RBX 用于索引 B c 的 micro-panel 中的数据,一次获取并存储 6 个双精度数据到 XMM1~
                 XMM3 中.XMM4~XMM15 用于存储不断累加的中间结果.在此过程中,向量寄存器 XMM0 实际共存储过 4 个
                 A c 数据,XMM1~XMM3 共存储过 6 个 B c 数据.

                 4    多线程并行

                    常用的多线程并行手段有 OpenMP          [31] 和 POSIX Threads(pthread) [32] .BLIS 同时支持两种并行方式,且当前只
                 在 Level-3 实现了并行.与 OpenMP 相比,Pthreads 技术在实际编程中需要考虑临界区、线程同步原语等非常底
                 层的操作,故我们在 GEMM 以及 TRSM 等小 kernel 和 Level-1 与 Level-2 的并行化实现中,选择使用更加便利
                 的 OpenMP 进行并行化.
                 4.1   Control-tree优化
                                                                                  [7]
                    针对 Level-3 级 BLAS 的并行实现方式,BLIS 提出了新颖的“control-tree”结构 .该结构以统一的方式实现
                 了如图 3 所示的 GEMM 大 kernel 算法的并行以及 HERK、TRMM 和 TRSM 运算的并行.Control-tree 结构最突
                 出的优点是可以在任意层 loop 内分别使用环境变量 BLIS_JC_NT、BLIS_IC_NT、BLIS_JR_NT 和 BLIS_IR_NT
   11   12   13   14   15   16   17   18   19   20   21