天天看点

《高性能科学与工程计算》——1.6 向量处理器

本节书摘来自华章计算机《高性能科学与工程计算》一书中的第1章,第1.6节,作者:(德)georg hager gerhard wellein 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

从cray 1超级计算机开始,直到基于risc的高度并行计算机出现之前,向量机一直占据着科学计算的主要领域。在写这本书时,只有两家公司还在制造和销售向量机。但因为对内存带宽和运行时间有高度需求,向量机还是有着一个充满商机的市场。

根据设计,对于合适的可向量化的代码,向量处理器相较于标准的微处理器可以达到一个较好的实际性能。这种设计遵循单指令多数据(simd)的范例,即一条简单的机器指令被自动地应用于很多类型相同的参数。许多现代的基于cache的微处理器以扩展sisd指令集的形式采用这些技术(细节参考2.3.3节)。而且向量机在执行单元和存储子系统上有大量的并行操作。

1.6.1 设计原理

现代向量处理器与risc设计非常像,都是寄存器—寄存器型的机器:机器指令运行在向量寄存器上,每个向量寄存器存储长度在64~256(双精度)之间的一些参数。向量寄存器的宽度称为向量长度lv。对于每一种算术运算,像加法、乘法、除法等都分别有一条流水线,每一条流水线在每个指令周期都可以得到一定数目的结果。对于乘法和加法流水线来说,得到2~16个结果,这也称作多轨流水线(multitrack pipeline)(参考图1-21)。其他像平方根和除法操作比较复杂,流水线的输出要低很多。但是即使只有单一流水线的向量处理器也可以达到基于cache的超标量微处理器相同的峰值性能。为了向向量寄存器提供数据,有一个或者多个直接跟主存相连的读取、存储、读取与存储相结合的流水线。尽管最近像nec sx-9的设计引进了容量小的片上存储,但传统的向量cpu没有缓存层次的概念。

《高性能科学与工程计算》——1.6 向量处理器

为了在向量cpu上获得合理的性能,必须采用simd类型的指令。我们来看一个简单的例子,有两个数组a(1 : n?) = b(1 : n?) + c(1 : n?)。在一个基于cache的微处理器上,这个运算最终的实现是在a、b和c上的一个循环(可能会软流水),对于每一次计算,必须要执行两次读取操作、一个加操作和一个存储操作,并且还必须有整型和分支逻辑来实现循环。如果数组的长度比寄存器长度短,则向量cpu可以对于整个数组使用一条指令:

《高性能科学与工程计算》——1.6 向量处理器

https://yqfile.alicdn.com/47b331d6e0973eee7806b13070e0146e8062b084.png" >

在这里,v1、v2、v3表示向量寄存器。追踪分散在不同的流水线上的向量索引的工作是自动完成的。如果数组的长度比向量的长度大,循环就必须以向量长度为单位分块执行:

《高性能科学与工程计算》——1.6 向量处理器

这个工作由编译器自动完成。

像向量加这样的操作并不需要等到向量寄存器将所有参数都准备就绪才开始运算,而是可以在最初的一些参数就绪之后就可以开始执行。这个特征称为链接(chaining),这也是不同管道(例如乘法、加法)能够同时操作的必要条件。

很明显,在risc出现前向量结构明显地降低了指令发射速率的要求,那个时候多发射超标量处理器还没有足够快的指令cache。更重要的是,读取/存储操作的速率需要和cpu核频率匹配,所以为运算流水线提供数据就更不是问题了。由于现代内存芯片在一次缓存操作后需要几个时钟周期的恢复时间(也称为bank忙碌时间),所以可以通过有大量的bank结构的内存布局来实现两者速率的匹配。为了减小两者之间的差距,现代向量机提供数以千计的内存bank,这也导致了这种结构对于通用计算来说相当昂贵。总之,向量处理器是通过高度并行的流水线以及高带宽的内存访问来获得它的性能。

编写程序以使编译器产生有效simd向量指令称为向量化。有时候需要代码重构或者在源代码中插入指令指针来帮助编译器确认simd并行。每一个向量处理器都有一个单独的标量单元,用来执行那些不能向量化的代码(接下来的章节中讨论)并完成任务管理工作。向量处理器中的标量单元要比标准的risc或基于x86设计中的标量单元差很多,所以为了获得高性能,向量化就显得格外重要。如果代码不能被向量化,则使用向量机不会带来任何好处。

1.6.2 最高性能估计

向量处理器的峰值性能可以通过加法和乘法流水线的track数目以及时钟频率得到。比如,一个2ghz的向量处理器以及具有4个track的流水线,峰值性能是:

《高性能科学与工程计算》——1.6 向量处理器

求平方根、除法和其他操作由于有着较差的吞吐量,对计算峰值性能没有较大的贡献,所以在这里不予考虑。关于内存带宽,有4个track的ld/st(参见图1-21)流水线可以得到的读写带宽为:

《高性能科学与工程计算》——1.6 向量处理器

这恰好是nec sx-8处理器的标准规格。和基于cache的标准微处理器相比,向量处理器的内存接口的频率通常和核相同,能够为峰值性能提供更高的带宽。注意到上面这些计算都是建立在一个假设上:向量处理单元一定会被用到——如果代码是不可向量化的,因为要受标量单元的限制,所以不管峰值性能或者峰值存储带宽都不能达到。

通常对于一个拥有简单内存访问类型的循环,其性能可以预测。第3章将会对平衡分析给予详细介绍,例如对结构和循环代码的特点进行性能预测。对于向量处理器,由于不存在cache,所以预测一般会比较简单。以代码清单1-1为例,3次读取操作,1次存储操作和两个浮点操作(加法和乘法)。由于只有单一的ld/st管道,读取和存储操作,甚至对不同数组的读取操作都不能够重叠。但它们可以重叠算术管道并链接到算术管道。在图1-22中,长平行四边形代表在向量寄存器上的一个操作,标志着管道操作的执行(与图1-5中的时间线很像)。首先必须向一个向量寄存器中从数组c读取数据,ld/st管道开始于用数组d中的数据填充向量寄存器,乘法管道就可以开始在c和d上执行算数运算。只要来自b的数据可用,加法管道就可以计算出最终结果,继而ld/st管道将结果存储到内存。

《高性能科学与工程计算》——1.6 向量处理器

整个过程的性能瓶颈很显然是ld/st流水线。如果给予合适的代码,硬件能够在相同时间内执行4倍的乘法和加法指令(图1-22中浅灰色菱形),所以代码清单1-1的性能只能达到峰值性能的25%,在上面描述的向量处理器上性能为4 gflop/s,这与图1-4中的sx-8 n很大时的曲线完全吻合。需要注意的是,由于向量系统上有很大的内存延迟,所以这个限制只对相对大的n值可以达到。另外,除了不可向量化的代码,短循环是第二大影响这些结构的性能因素。

1.6.3 程序设计

向量化的必要条件是在循环的迭代之间不存在真数据相关。软流水(见1.2.3节)也同样不能出现真数据相关,例如允许向前引用(forward reference)但是向后引用(backward reference)会影响向量化。更精确地讲,真相关的位移间隔必须大于某一阈值(至少是向量的长度,有时更大),这样前面向量操作的结果才会是可用的。

因为与“单条指令”的编程规范冲突,内部循环中的分支也会影响向量化。但是我们有一些方法来支持向量化循环中的分支:

掩位寄存器(mask register,本质上是向量长度的布尔寄存器)用来实现循环迭代的选择性执行。我们来看下面一个例子:

《高性能科学与工程计算》——1.6 向量处理器

首先,使用逻辑流水线根据分支条件产生一个每位为布尔类型的向量。接下来这个向量被用来从if或者else分支中选择结果(见图1-23)。当然,如果有开销大的操作,那么所有的循环分支都被执行显然是一种浪费,但是向量化的利大于弊。

《高性能科学与工程计算》——1.6 向量处理器

https://yqfile.alicdn.com/6c112693736061b283fe53ae6d04fa280bc6c6d8.png" >

对于单个分支(没有else部分),特别在包括像除和平方根这些操作的情况下,gather/scatter是一种向量化的有效方法。在下面的例子中,如果分支预测大部分情况为假时,像图1-23中那样使用掩位寄存器会浪费很多计算资源:

《高性能科学与工程计算》——1.6 向量处理器

与掩位寄存器不同(参见图1-24),所有必要的元素首先被收集到向量寄存器中(gather),然后执行向量操作,最后执行的结果被存储回去(scatter)。

《高性能科学与工程计算》——1.6 向量处理器

编译器会自动执行向量化操作(可能是源代码直接支持向量化)或者代码被重写使得可以显式地使用临时数组来保存所需的向量数据。还有另一种替代方案,使用列表向量,它是一个整型的向量数组,保存着条件为真的索引,通过间接访问,这些索引可以用来重构原始循环。

由于cache行的概念,基于cache处理器对gather/scatter操作开销非常大,而向量处理器能更经济地执行(即使跨度为1的访存模式更有效)。

关于向量结构的编程和优化可以从生产商处获得更多参考文档[v110,v111]。