多标量处理器 Multiscalar(翻译自文章Multiscalar Processors)

多标量处理器 Multiscalar

在多标量处理器中,程序被分成由软硬件组合而成的任务的集合。这些任务被分配给处理器内的并行单元进行处理。论文从多标量处理器的设计思想,多标量程序的结构,多标量处理器的硬件结构,多标量处理器周期分配,多标量处理器模型性能评估五个方面介绍了多标量处理器。

多标量的设计思想

多标量处理器一种新的处理器实现范例,能够从普通的高级语言中提取大量的指令级并行。

多标量处理器微架构

多标量处理器的一种微架构如下图一所示:

                             

图1    多标量处理器的一种架构

 

我们可以将多标量处理器理解成许多处理单元的集合,通过定序器给处理单元分配任务。一个程序被分成由软硬件组合而成的任务的集合,一个任务是CGF的一部分,这个任务的执行对应着一些连续的动态指令序列,通过注释器,程序被静态的划分成多个任务。

多标量处理器的执行过程

多标量处理器的一个目标是非顺序执行控制流图(CGF)。下图是一个CGF的示例:

 

图2 CGF示例

 

多标量处理器是通过确立一个大的准确的动态指令窗口,提取和运行独立的指令。多标量是大步的执行CGF,不是一条指令一条指令的执行,也不是一个基本块一个基本快的执行,而是一个任务一个任务的执行。

执行的过程是多标量处理器将任务T1赋给一个处理单元,而不用管任务的内容是什么,不用管任务T1和其他任务之间是否存在数据依赖或是是否存在控制依赖,然后预测任务T1执行完后将会执行哪个任务T2,然后将T2付给另一个处理单元(由尾指针指示)。以此类推,直到没有空闲的处理单元。可以通过一个例子来看多标量的处理过程。一个程序被划分成五个基本块五个基本块,ABCDE,如下图4所示:

3程序的基本块。

其中基本块的上标和下标分别代表外层循环和内层循环。每一个外层循环可以看成一个任务,赋给一个处理单元。那么处理器就可以同时有三个循环可以并行执行。因此处理器每个周期可以并行的执行三条指令,但是为了保证正确执行,任务执行必须保持顺序语义。

多标量程序

多标量程序示例

下面这是一段普通的高级语言代码:

这段程序是重复的从buffer中拿出一个符号(symbol),向下检查链表中有没有和这个符号匹配的符号。如果匹配,那么就进行处理,如果不存在匹配的符号,就在链表中添加一个记录,表示这个符号。

通过对这段普通的程序进行处理,可以将普通的高级语言的程序转化为多标量程序。

对程序添加任务描述符和标志位,可以得到多标量程序。

多标量程序的特点

多标量程序里维持的三种不同信息:

1.构成任务的真实的代码:这里指ISA(指令集架构)。

ISA对多标量的设计影响很小,所以现有的指令集架构可以基本不用做大的修改就可以使用

2.CGF结构的特点,这里主要指定序器

3.不同任务之间的通信特点。

定序器需要一些信息来快速执行CGF,这些信息主要指后继任务的信息,这新信息被静态的确定,并被保存才任务描述器中。为了协同不同任务之间的执行,有必要描述每个任务的特色,也就是每个任务产生的值,每个任务需要的值。编译器执行静态分析CGF的操作,提供create mask的值(这个任务将产生的值)。译器需要标记任务中最后一条更新寄存器的指令最后一条指令(退出指令)分别为forward位和stop位标记。

需要的主要硬件

多标量处理器需要添加的硬件主要有两个:定序器(Sequencer)和地址解析寄存器(ABR)。ABR的主要任务是存储内存投机操作,检查内存依赖,进行初始化修正工作,标记loadstore操作(用loadstore位)。

检查loadstore位可以检查依赖,若后面的一个任务取了内存地址的值,但是这个值前一个任务还没有产生,则产生内存依赖。

并行执行时数据依赖问题

需要注意的是不同任务的指令虽然是分离的,但是不是独立的。

程序顺序执行时,值是放在存储地址上,也就是内存和寄存器上。因为顺序执行时,将存储地址看成单一的寄存器和内存地址,同时也有时间上的顺序,就是说第二个循环用到的数据,是第一个循环产生的,这种顺序不能改变,所以多标量也必须保持这种观点。

那么多标量处理器执行时应该考虑指令之间的依赖关系。指令之间是存在控制依赖和数据依赖的,所以一个问题就摆在了我们面前,任务之间是如何通信,如何解决这些依赖。

解决指令之间的依赖问题,维持程序的顺序依赖主要有下列两种策略。第一种策略是保证任务内是顺序的,第二种策略是处理单元之间保持一个宽松的顺序语义,反过来给任务之间施加了一个顺序,也就是处理单元连成一个环,头执行第一个任务,尾执行最后一个任务。

数据依赖主要有两种,一是寄存器之间的依赖,另一个是内存依赖。

寄存器依赖

解决寄存器依赖是控制逻辑通过寄存器组构成的保留站来同步前面任务产生的值和后面任务需要的值来解决的。Creat mask用来存放前一个任务产生的值。通过图一所示的那个单向环传递产生的值。

当前面的任务产生的值传给了后面的任务时,保留站就应该清空

当前面的任务的值还没有产生出来时,后面的任务需要等待。

内存依赖

至于存储器,情况就不太一样了。不同于寄存器,存储器不能提前精确的决定一个任务什么时候产生值或者什么时候需要值。

如果存储器知道需要的值的话,那么就可以向寄存器似的,经过同步,获得前一个任务产生的值。

但是大多数情况存储器是不知道的需要的值。有两种策略解决这个问题,保守的策略和积极的策略。保守的策略就是等待直到前一个任务产生正确的值,这样程序就能维持顺序语义。积极的策略就是:通过投机执行,在程序执行过程中,动态检查,如果发现错误,即程序使用了错误的数据,那么就取消程序,那么后继的任务也被取消。多标量使用的是积极的策略。

多标量的执行过程是投机执行,将其看做投机执行的原因是多标量执行过程中存在控制投机和数据依赖。控制投机是预测下一个任务,而数据依赖指的是程序可能用错误数据。

多标量周期分配

我们现在来看一个更加细节的问题,也就是多标量的执行时的周期分配问题。最好的情况是处理器执行的周期都是有效周期,然而这种最好的情况是不可能的,主要是因为并非所有的周期都是有效周期。多标量处理器中存在三种周期:无用周期,没有计算的周期,空闲周期。

无用周期

处理单元使用了错误的数据,或者执行了错误的预测分支。

没有计算的周期

处理单元在这个周期等待,等待前驱任务产生的值,或者等待同一个任务产生的值。

空闲周期

没有给处理单元分配任务(大多数情况是由于取消任务时,重新分配任务造成的)。第三种情况忽略不计。这里需要区分没有计算的周期(已经分给任务)和空闲周期(没有分配给任务)。

从周期分配的角度提高系统性能

既然多标量周期可以分为以上三种,所以可以尽可能能的提高有效周期的数量,来提高处理的性能。

对于无用周期,那么处理单元必须取消已经执行了的操作以保证正确,为了减小这种回滚带来的代价,应该做如下两种操作。第一,通过同步数据通信来减低取消操作的机会。第二,提早决定,防止进行了更多的无效操作

产生没有计算的周期原因是主要是任务内依赖,任务间依赖,取平衡。其中任务内依赖可以通过代码调度,乱序执行,非阻塞式cache解决。

性能评价

模拟环境特征

模拟器执行了多标量处理器的所有操作,执行了除了系统调用之外的所以程序代码。

处理单元5级流水结构,指令执行可以是有序和乱序。

存储器访存时间:访问前四个字10周期,以后每四个字10周期

定序器:包含1024个直接映射的cache

基准程序

加速比较高的基准程序

Eqntotteqntott中的大部分指令在函数cmppt中,是一个循环,编译器自动的包含整个大的循环,允许不同的处理单元执行不同的迭代。

Tomcatv:几乎所有的时间花在一个大的循环,这个循环的每个迭代都是独立的,因此我们获得了很好的加速。

Cmpwc:几乎所有的时间均花在一个循环,循环内包含其他的小循环。

Example80%的时间花在找那个那段代码上(就是这一部分演示的那个代码,找symbol的那个),因为外层循环的迭代几乎是独立的,所以加速比也不错。

加速比较差的基准程序

Compress:几乎所有的时间花在一个大的单一的循环上,包含了一个复杂的控制流。整个程序包含一个很深的关键路径,并且这个程序需要很大的hash表,导致了cache不命中率比较大,所以加速比不是很好。

Gccxlisp:取消任务(因为预测错误和存储器依赖)导致性能降低。多标量执行时间的花销(包括指令延时和数据cache延时)也导致性能的降低。

结论:

这篇论文主要描述了多标量处理模式,这是利用细粒度并行也就是指令级并行的一种新模式。

多标量处理器使用软硬件结合的方式从普通的程序利用并行。它通过程序控制流图把程序划分任务,而不用检查任务的内容,任务被分配给了不同的处理单元,每一个处理单元取指和执行分给它的任务。

当作者在研究多标量处理器时,多标量处理器还存在一些有待改进的地方。比如继续改进编译器,使之和硬件更好的配合。

作者认为多标量处理器性能问题还不是很好,是由于他们的解决方法还是有待于改进。希望是随着软件支持的改进,和更合理的硬件,多标量处理器能够更好的利用指令级并行,以至于超过其他的模式。

你可能感兴趣的:(Multiscalar)