目录
重要知识点:
1. 流水线实际CPI
2. 基本程序块
3. 循环级并行
4. 循环展开
5. 数据流
把握要点:
6. 流水线处理的实际CPI和基本程序宽度该奶奶
7. 循环级并行和基本的开发技术
8. 相关与流水线冲突以及解决方法、程序顺序
9. 数据流和异常行为,这里需注意使用并行化技术进行改进,其结果要与串行执行的结果一致
本章研究目的
在流水线思想的基础上进一步扩展,开发出更多的指令集并行
1. 指令级并行的概念(ILP)
- 指令集并行:指令间存在的潜在并行性。(几乎所有的处理机都利用流水线来使指令重叠并行执行,以达到提高性能的目的。)
- 开发ILP的方法主要有两种:基于硬件的动态开发方法和基于软件的静态开发方法(这两种方法可以混用,动态和静态相结合才能充分的开发程序中潜在的ILP)
- 流水线处理机的CPI等于理想流水线的CPI加上各类停顿的时钟周期数:
理想CPI是衡量流水线最高性能的一个指标,IPC是每个时钟周期完成的指令条数。 - 基本程序块:如果一串连续的代码除了入口和出口外,没有其他的分支指令和转入点,则为一个基本程序块(一个程序块中可并行的指令过少,必须跨越多个基本程序块开发ILP)
- 循环级并行性:开发循环的不同迭代之间存在的并行性【增加指令之间的并行性最简单和最常用的方法】
- 每一次循环都可以和其他循环重叠并行执行
- 每一次循环内部却是没有任何的并行性可言
- 循环型并行性转换为ILP的方法:
- 循环展开处理
- 采用向量指令和向量数据表示
- 确定程序中指令之间存在什么相关,对于确定程序中有多少并行性以及如何开发这些并行性具有重要意义。
- 相关是程序固有的一种属性,他反映了程序中指令之间的相互依赖关系。而具体的一次相关是否会导致实际冲突以及该冲突会带来多长时间的停顿,则是流水线的属性。
- 克服相关限制开发ILP:
- 程序顺序:由源程序确定的在完全串行方式下指令的执行顺序。(只有在可能影响程序正确性的情况下才保持程序顺序)
- 对于正确执行程序来说,必须保持的最关键的两个属性:数据流和异常行为。
- 保持异常行为:无论怎么改变指令的执行顺序都不能改变程序中异常的发生情况。(经常被弱化为:指令执行顺序的改变不能导致程序中发生新的异常)
- 数据流:数据值从其生产者指令到其消费者指令的实际流动。【分支指令使得数据流具有动态性,因为它使得给定指令的数据可以有多个来源】
- 如果能做到保持程序的数据相关和控制相关,就能保持程序的数据流和异常行为。(控制相关的影响不大:原因见教材108页最后一段)
- 仅仅保持数据相关性是不够的,因为一条指令有可能数据相关于多条先前指令,所以只有再加上保持控制顺序才能保持程序顺序,知道哪条指令是真正所需数据的生产者。
通过保持控制相关可以避免对数据流的非法修改,所以DSUBU不能被移到分支指令之前。有时发现不遵守控制相关既不影响异常行为,也不改变数据流,这是就可以大胆地进行指令调度,把失败分支中的指令调度到分支指令之前。
- 前瞻执行不仅能解决异常问题,而且能够在保持数据流的情况下减少控制相关对开发ILP的影响。
2. 指令的动态调度
2.1 动态调度的基本思想
- 指令顺序执行:
指令放入流水线的顺序和指令完成的顺序一致 - 指令乱序执行:
指令放入流水线的顺序和指令完成的顺序不一致,也就是有些指令进入流水线后呗阻塞,而在其后进入流水线的指令先完成了 - 简单流水线的局限性:
其指令是按程序顺序流出和按序执行的。
第一个问题
在之前的简单流水线中,只有当既没有数据冲突也没有结构冲突的时候才能继续流出指令。
不采用定向技术顺序流动,产生数据冲突后,后面的指令被阻塞不动。所以后面从IF开始执行。
如果想要让ADD指令流出,需要进行一些改进:原来数据冲突和结构冲突的检测都在ID段,现在必须把指令流出的工作拆分为两个步骤:【🐷检测结构冲突、等待数据冲突消失🐷】。只要检测到【没有结构冲突】就可以让指令流出,并且流出的指令一旦其操作数就绪即可执行。
现在指令的执行顺序与程序的完成顺序不同,指令的完成也是乱序的。
第二个问题
指令的动态调度导致了指令的乱序执行,指令的乱序执行导致了有反相关和输出相关的指令进入流水线之后产生了读后写冲突和写后写冲突。
- 采用动态调度的流水线支持多条指令同时处于执行当中,这是动态调度的一大优点。但这要求具有多个功能部件,或者流水线功能部件,或者两者兼而有之,后面假设具有多个功能部件。
总结
- 动态指令调度:相对于静态指令调度,动态指令调度是在指令的执行阶段中进行调度,使得无关的指令得以先执行,减少阻塞。且能够处理一些在编译时情况不太明确的相关(如存储器访问的相关)
- 动态指令调度将会引起指令乱序执行,因此使用换名技术消除名相关(包括反相关和输出相关)
- 指令乱序完成带来了另一个问题:难以处理异常
- 动态调度要保持正确的异常行为(只有那些在程序严格按程序执行时才会发生的异常,才能真正发生)。动态调度的处理机是这样来保持正确的异常行为的:对于一条会发生异常的指令来说,只有当处理机确切的知道该指令被执行后,才允许它发生异常
- 不精确异常:当执行指令i导致发生异常时,处理机的现场跟严格按程序顺序执行是指令i的现场不同。反之为精确异常(不精确异常使得在异常处理后难以接着继续执行程序)
- 发生不精确异常的原因:流水线可能已经执行完按程序顺序是位于指令i之后的指令;流水线可能还没有完成按程序顺序是指令i之前的指令。
2.2 Tomasulo算法
要点:
1. 核心思想
2. 基本结构
3. 寄存器换名的实现
4. Tomasulo算法的特点
5. 执行步骤
2.2.1 Tomasulo算法的核心思想
- 记录和检测指令相关,操作数一旦就绪就立即执行(没有就绪则等待),把发生RAW冲突的可能性降到最小。
- 通过寄存器换名来消除WAB冲突和WAW冲突
2.2.2 基本结构
-
保留站
- 设置在运算部件的入口。浮点加法器的保留站有3 个,浮点乘法器的保留站🈶️ 2个。每个保留站都有一个标识字段,唯一地标识了该保留站。每个保留站中保存一条一斤共流出并等待到本功能部件执行的命令。【内容:操作码、操作数以及用于检测和解决冲突的信息】
- 在一条指令流出到保留站的时候,如果该指令的源操作数已经在寄存器中就绪,则将之取到该保留站中;如果该操作数还没有计算出来,则在该保留站中记录将产生这个操作数的保留站的标识。
- CDB连接到除了load缓冲器以外的所有部件的入口,浮点寄存器通过一对总线连接到功能部件,并通过CDB连接到store缓冲器的入口。
-
公共数据总线CDB.
- 一条重要的数据通路
- 所有的功能部件的计算结果都是送到CDB上,由它吧这些结果直接播送到各个需要该结果的地方
- 在具有多个执行部件且采用多流出(即每个时钟周期流出多条指令)的流水线中,需要用多条CDB
-
load和store缓冲器
- load和store缓冲器中存放的是读/写存储器的数据或地址
- load缓冲器的作用:
a.存放用于计算有效地址的分量
b.记录正在进行的load访存,等待存储器的响应
c.保存已经完成了的load结果(即从存储器取来的数据),等待CDB传输 - store缓冲器的作用:
a.存放用于计算有效地址的分量
b.保存正在进行的store访存的目标地址,该store正在等待存储数据的到达
c.保存该store的地址和数据,直到存储部件接收
-
浮点寄存器
共有16个浮点寄存器:F0、F2、F4…F30。它们通过一对总线连接到功能部件,并通过CDB连接到store缓冲器。
-
指令队列
指令部件送来的指令放入指令队列,指令队列中的指令按先进先出的顺序流出
- 运算部件
浮点加法器完成加法和减法,浮点乘法器完成乘法和除法操作
2.2.3 寄存器换名技术
寄存器换名是通过保留站和流出逻辑共同完成的。当指令流出时,如果其操作数还没有计算出来,则将该指令中相应的寄存器号换名为将产生这个操作数的保留站的标识。所以指令流出到保留站后,其操作数寄存器号或者换成了数据本身(若已就绪),或者换成了保留站的标识,不再与寄存器有关系。
2.2.4 Tomasulo算法
1. 通过例子来解释Tomasulo算法
- Tomasulo算法采用分布的保留站,具有2个特点:
- 冲突检测和指令执行控制是分布的。每个功能部件的保留站中的信息决定了什么时候指令可以在该功能部件开始执行。
- 计算结果通过CDB直接从产生它的保留站传送到所有需要它的功能部件,不用经过寄存器。
2.指令执行步骤(3步)
A.流出
【其实思路都是在错误顺序的写入之前先占个坑】
- 从指令队列的头部取一条指令。
- 如果该指令的操作所要求的保留站有空闲的,就把该指令送到保留站。并且如果其操作数在寄存器中已经就绪,就将这些操作数送入保留站r,如果其操作数还没有就绪,就把将产生该操作数的保留站的标识送入保留站r。这一步实际上进行了寄存器的换名和对操作数进行缓冲,消除了WAR冲突。
- 另外还要完成
对目的寄存器的预约工作
,将之设置为接收保留站r的结果。这一步实际上相当于完成了写操作(预约)(提前完成了写操作虽然不知道结果,但是把保留站表示暂时作为结果看待),由于指令是按程序顺序流出的,当出现多条指令写同一个结果寄存器时,最后留下的预约结果肯定是最后一条指令
的,消除了WAW冲突。
B.执行
load和store指令的执行需要两个步骤:
- 计算有效地址(要等到基地址寄存器就绪)
- 把有效地址放入load和store缓冲器
load缓冲器中的load指令的执行条件:存储部件就绪
store缓冲器中的store指令的执行条件是:执行前必须等到存入存储器的数据到达
通过按顺序进行有效地址计算来保证程序顺序
,这有助于避免访问存储器的冲突。
C.写结果
- 功能部件计算完毕后,就将计算结果放到CDB上,所有等待该计算结果的寄存器和保留站(包括store缓冲器)都同时从CDB上获得所需要的数据。
- 保留站、寄存器组和load/store缓冲器都包含附加标志信息,用于检测和消除冲突。不同部件的附加信息略有不同。标识信息实际上就是用于换名的一组虚拟寄存器的名称(编号)。【特殊编号0用于表示寄存器中的操作数就绪。】
- 在Tomasulo算法中将保留站作为扩展的虚拟寄存器;在别的方法中也可以采用其他的存储单元作为虚拟寄存器,例如采用额外的寄存器或者类似于后面将要介绍的再定序缓冲器结构等
3. 举例
在指令进入保留站后会对操作数是否就绪进行检测,就根据寄存器状态表。若就绪直接将浮点寄存器中内容填入操作数。若两个寄存器都就绪可流出,但流出之前还要记得对目标寄存器进行预约,即将该指令所在保留站站号写入寄存器状态表里。
- 与其他动态调度方法相比,Tomasulo算法具有以下两个主要优点:
- 冲突检测逻辑是分布的(通过保留站和CDB实现)。多个地方等待这个结果,可以同时获得。如果使用的是集中的寄存器组,各条指令就要等结果写入寄存器,然后再依次顺序从寄存器组读出。
- 消除了WAW和WAR冲突导致的停顿。这是通过使用保留站进行各个寄存器换名,并且在操作数一旦就绪就将之放入保留站来实现的。
4. 具体算法
(1)指令流出
【a】浮点运算指令
进入条件:有空闲保留站r
【2】load和store
进入条件:缓冲器有空闲单元r
(2)执行
【1】浮点操作指令
【2】load/store指令
(3)写结果
【1】浮点运算指令和load指令
【2】store指令
Tomasulo算法总结:
- 当浮点运算指令流出到一个保留站r时,把该指令的目标寄存器rd的状态表项置为r,以便将来从r接收运算结果(相当于进行了预约或者定向),操作数如果已经准备好直接放在V字段,否则就要等待其他保留站产生操作数(这里如果不是已经清晰了的结果就一定是保留站)。当指令执行完成且CDB就绪,就可以把结果写回,把数据放到CDB上,所有需要数据的部分可以在同一个时钟周期内同时接收该结果(广播)。操作数因此而备齐的指令可以在下一个时钟周期开始执行。
- 在Tomasulo算法中,load/store指令的处理和浮点运算指令有些不同,只要load缓冲器有空闲单元,load指令就可以流出。
- 该算法对指令的执行有个限制:如果流水线中还有分支指令没有执行,那么当前指令就不能进入“执行”阶段。这是因为在“流出”阶段后,程序顺序就不再被保证了。所以为了保持正常行为,必须加上这个限制。【前瞻执行可以消除限制】
- 对于多流出的处理机来说,随着流出能力的提高以及设计者们更多地关系难以静态调度的代码的性能,寄存器换名以及动态调度技术就变得越来越重要了。特别地,Tmoasulo算法还是硬件前瞻执行的基础,因此该算法得到了广泛的应用。
3. 动态分支预测技术
当开发的ILP提高时,控制相关就会成为主要的限制因素之一,所开发的ILP越多,控制相关的制约就越大,分支预测就要有更高的准确度。本节方法对于每个时钟周期流出多条指令的处理机来说是非常重要的。
要点:
1. 动态分支预测技术和静态分支预测技术的区别
2. 什么叫“分支指令过去的表现”?
3. 如何衡量分支预测的有效性?
4. 分支预测要解决的问题是什么?
问题解答:
- 静态分支检测技术所进行的操作数事先预定好的 ,与分支的实际执行情况无关;动态分支预测的方法在程序运行时根据分支执行过去的表现预测其将来的行为(如果分支行为发生了变化,预测结果也跟着改变,此外有更好的预测准确度和适应性)。
- 就是记录分支的历史信息,在预测错误时,要作废已经预取和分析的指令,恢复现场,为了恢复现场,需要在执行预测的目标指令之前将现场保存起来。
- 分支指令的有效性不仅取决于其准确性,而且与正确和不正确两种情况下的分支开销都有密切关系。这些分支开销是由流水线结构、预测的方法和预测错误时的恢复策略等诸因素决定的。
- 动态分支预测技术的目的有两个:预测分支是否成功和尽快找到分支目标地址(或指令),从而避免控制相关造成的流水线停顿。【需要解决的关键问题:1. 如何记录分支的历史信息 2. 如何根据这些信息来预测分支的去向(甚至取到指令)】
3.1 采用分支历史表BHT
- 分支历史表:(分支预测缓冲器)这种方法是最简单的动态分支预测方法。它用BHT来记录分支指令最近一次或几次的执行情况(成功或不成功),并据进行预测。
【为了提高预测的准确度,常采用两位二进制位来记录。
- 两位分支预测中的操作有两个步骤:分支预测和状态修改(当分支指令到达译码段【ID】时,根据从BHT读出的信息进行分支预测)
- ==在BHT方法中,只对分支是否成功进行预测,对分支目标地址没有提供支持。==所以只在如下情况有用:判定分支是否成功所需的时间大于确定分支目标地址所需的时间。在前述5段经典流水线中,由于判定分支是否成功和计算目标地址都是在ID段完成,所以BHT方法不会给该流水线带来好处。
- 一般采用4K的BHT就可以了,BHT可以跟分支指令一起存放在指令Cache中【在取址阶段把历史位一起读出来】,也可以用一块专门的硬件来实现【在取指令的同时用指令地址的地位(低12位)去访问BHT,读出历史位。
3.2 采用分支目标缓冲器BTB
-
背景:
在高性能流水线中,特别是多流出的处理机中,只准确地预测分支还不够,还要能够提供足够的指令流。许多现代的处理器要求每个时钟周期能提供4~8条指令。这需要尽早知道分支是否成功、尽早知道分支目标地址、尽早获得分支目标指令。 -
分支目标缓冲器(BTB)/分支目标Cache(BHT是在ID段进行预测得到分支目标地址,在IF段得到下一条指令的地址,如果能提前1拍(即在IF段)就知道这些信息分支开销就可以减少位0)
-
BTB的流水线相关操作:
-
分支指令可能存在3种情况:
- 上一次分支成功的指令,在BTB表中会有记录,这次仍预测它是成功的,直接用缓冲的分支目标地址取分支目标指令
- 以前没执行过的分支指令,在BTB表中不会有记录,那没办法,咋预测只能随缘,但是如果这次预测成功了,要将该指令的地址和对应的分支目标指令写入BTB
- 上次分支指令不成功的指令,同(2)
-
BTB的另一种形式:
- 在分支目标缓冲器中存放一条或多条分支目标处的指令。有的实现方案还保留了分支目标地址,有的则将之去掉了。
- 这种方案潜在的好处:更快地获得分支目标处的指令;可以一次提供分支目标处的多条指令,这对于多流出处理器是很有必要的;可以进行【分支折叠】 的优化,该优化可以用来实现0⃣️延迟无条件分支,甚至有的时候还可以做到零延迟条件分支。
基于硬件的前瞻执行
要点:
1. 分流出、执行、写结果、确认四个阶段
2. 顺序流出、准备好的先执行,顺序确认
3. 分支预测错误,刷新ROB
4. 异常指令到达ROB头部,刷新ROB
- Tomasulo算法能够做什么?不能够做什么?
能做:1. 解决RAW冲突 2. 消除WAW和WAR冲突
不能做:1. 分支预测 2. 处理异常 - 背景:
前瞻执行能够更好的解决控制相关的问题。它对分支指令的结果进行猜测总是对的,然后按照这个猜测结果继续取,流出和执行后续的指令。只是执行指令的结果不是写回寄存器或存储器,而是放到一个称为ROB的缓冲器中,等到相应的指令得到“确认”后,才将结果写入寄存器或存储器。 - 硬件的前瞻执行是把3种思想结合在了一起:
- 动态分支预测。用来选择后续执行的指令
- 在控制相关的结果尚未出来之前,前瞻的执行后续指令
- 用动态调度对基本块的各种组合进行跨基本块的调度
- 前瞻执行的重要思想:前瞻执行允许指令乱序执行,但是要求程序顺序确认。并且在指令被确认之前,不允许它进行不可恢复的操作,如更新计算机状态或发生异常。
- 前瞻执行的基本思路
- 顺序流出指令队列
- 猜测路径和乱序执行
- 顺序确认(保证顺序写回)
- 猜错或发生异常,没写回的全部不算数
- 前瞻执行机制的具体执行情况:
产生异常的指令:在该指令到达ROB头部前不处理,到达ROB头部时,产生中断,清空ROB,此操作能够完成精确的异常处理。
个人认为:但凡刷新ROB,连同指令队列一起刷新,因为假如指令队列中有指令已经是有问题的指令流。
例题
首先应该画出当前执行的状态表:
这里可以很明显的看出:虽然后面的指令都已经完成了执行但是没有得到确认,因为确认是需要按照顺序执行的,所以由增加的ROB实现了顺序完成。
- 由于前瞻执行通过ROB实现了指令的顺序完成,所以它不仅能够进行前瞻执行,而且能够实现精确异常。比如上面的MUL引起异常,先不予理睬,只要等到它到达ROB的头部,再对该异常进行处理,同时清除所有正在执行的指令,这样就实现了精确的异常。
- 尽管这里是针对浮点的,但可以很容易地推广到整数寄存器和整数功能单元上。实际上前瞻执行对整数程序更有效,因为这些程序中的分支特征更不容易预测(更不容易预测结果的采用动态的预测,随着程序运行进行判断就可以提高准确度)。
- 基于硬件的前瞻和动态调度相结合,可以做到系统结构相同但实现不同的机器能够使用相同的编译器。
- 前瞻执行的主要缺点是所需硬件太复杂。
4. <span id='4>多指令流出技术
要点:
1. 多指令流出技术
2. 超标量
3. 超长指令字
4. 基于静态调度的多流出技术
5. 基于动态调度的多流出技术
前面的两种方法能够减少数据冲突和控制冲突导致的停顿,使CPI尽可能为1的理想情况。如果想要进一步提高性能,使CPI小于1,就必须采用多指令流出技术。
4.1 基于静态调度的多流出技术
限制超标量处理机的时钟频率提高的因素
1. 指令流出段
因为在指令流出的时候进行冲突检测,检测很难在一个时钟周期内完成,当提高指令流出的速率时,指令流出段有可能会成为指令流水线的瓶颈。虽然分成两段流水比较直观,但是进一步划分就不大容易了。
以同时流出一条整数型指令和一条浮点型指令为例,其中把load指令、store指令、分支指令也归类为整数指令。
前提要求:能够同时取两条指令(64位),也能同时译码两条指令(64位)
【高性能超标量处理机一般是依靠一个独立的指令预取部件来提供足够的指令流】
对指令的处理步骤(3‘):
- 从Cache中取两条指令
- 然后确定那几条指令可以流出(0~2)
- 最后把它们发送到相应的功能部件
注:由于是“1条整数指令+1条浮点运算指令”所以大多数流出包内的冲突消除了。
- 相关内容的改进:
- 要提高其浮点运算能力,可以采用流水浮点运算部件,也可以采用多个独立的浮点运算部件,使其与超标量处理机提高的浮点指令的流出速率相匹配。
- 该种并行流出的方式,需要增加的硬件很少。因为【整数指令和浮点指令使用不同的 寄存器组和不同的功能部件(就可以使用已有系统的不同的部分不用因为争夺同一个部分导致结构冲突,而去在copy同样的一个部件,这个角度看需要增加的硬件很少)】
- 增加冲突检测逻辑电路
- 浮点load或浮点store指令与浮点操作指令并行流出的情况,这会导致浮点寄存器的访问冲突。因此需要【增设一个浮点寄存器读/写端口】
- 由于流水线中的指令多了1倍,定向路径也要增加
2. load指令的延迟
load指令有1个时钟周期的延迟,这就使load指令的结果不能被同时流出的指令所使用,也不能被下一个周期流出的两条指令所使用。load后续的3条指令都不能使用其结果,否则会引起停顿
3. 分支延迟
分支延迟为1个时钟周期。如果分支指令是流出包中的第一条指令,则其延迟是3个时钟周期;否则就是流出包中的第二条指令,其延迟就是2个时钟周期。
4.2 基于动态调度的多流出技术
- 动态调度不仅在有数据冲突的情况下能提高性能,而且还有可能克服指令流出所受的限制。动态调度可以使得指令流出时不受【每个时钟周期只能启动1个整数操作和1个浮点操作的执行】(前提是在保留站被全部占用之前)
- 假如对Tomasulo算法进行扩展,使之可以支持【双流出超标量流水线】,又不想乱序地向保留站流出指令(破坏程序语义),这样指令流出的硬件复杂度会增加。将整数所用的表结构与浮点所用的表结构分离开,分别进行处理
- 两种方法实现多流出:
- 在半个时钟周期里完成流出步骤,这样一个时钟周期就能处理两条指令(半个周期1条,一共两条,不是绝对并行)
- 设置一次能同时处理两条指令的逻辑电路。
现代的流出4条或4条以上指令的超标量处理机经常2种方法都采用
⚠️每种类型的指令在每个阶段都做了什么要了解!!!
- store和分支指令不经过写结果段,因为它们不写寄存器
- 对于load和store来说,在执行段是进行有效地址计算
- 对于分支指令来说,在执行段是进行转移条件判断,并检测分支预测是否正确。假设他所用的操作数一旦就绪,就可以立即执行
- 🐷下一个循环迭代中的load指令比当前迭代的store指令先访问存储器
实际上对于单流水线改进不大,原因是浮点运算少,ALU部件成了瓶颈。可以考虑增加一个加法器,把ALU和地址运算功能分开,但是这样做会出现多条指令同时写CDB的情况(CDB竞争),需要再增加一条CDB,但是这样硬件的复杂度增加,各个部件的使用效率又会座位考虑范围。
4.3 超长指令字技术
- 在指令流出时不需要进行复杂的冲突检测,而是依靠编译器在编译时找出指令之间潜在的并行性,并通过指令调度把可能出现的数据冲突减少到最少,并把能并行执行的指令组装成一条很长的指令。(这种指令通常100到几百位)
- 在VLIW处理机中一般设置有多个功能部件。相应的,指令字也被分割成一些字段,每个字段称为一个操作槽,直接独立地控制1个功能部件。(为了使功能部件充分忙碌,程序指令序列中应该有足够的并行性,从而尽量填满每个操作槽),这种并行性是依靠编译器来挖掘的。
- 在VLIW处理机中,所有的处理和指令安排都是由编译器完成的。
- VLIW存在的问题:(3‘)
- 程序代码长度增加
原因:为了提高并行性而进行的大量的循环展开;指令字中的操作槽并非总能填满
解决方法:采用指令共享立即数字段的方法,或者采用指令压缩存储、调入Cache或者译码时展开的方法 - 采用了锁步机制
原因:任何一个操作部件出现停顿时,整个处理机都要停顿,这是因为所有的功能部件是同步操作的。
解决方法:可以通过设置适当的硬件动态检测机制来允许指令流出后的非同步执行。 - 机器代码的不兼容性
原因:编译生成的代码利用了指令集的特点以及具体的流水线结构的细节(指令流出数目、功能单元延迟时间)来进行优化的。
解决方法:机器代码翻译或仿真的方法【解决所有移植问题的通用方法】(超标量处理机在这方面好很多)
- 程序代码长度增加
4.4 多流出处理器受到的限制
处理器中指令的流出能力是有限的,主要受以下3个方面的影响:
1. 程序所固有的指令级并行
最简单、最根本的因素,对于流水线处理器,需要🈶️大量的可并行执行的操作才能避免流水线的停顿。这里必须加上一些无相关的浮点指令。通常情况下,所需要的无相关指令数等于流水线的深度✖️可以同时工作的功能部件数
2. 硬件实现上的困难
- 需要大量的硬件资源,因为每个时钟周期不仅要流出多条指令,还要执行它们。
- 只增加运算部件是没有用的,因为这时处理器受限于存储器的带宽。
- 多端口、层次化的存储系统带来的复杂性和访问延迟可能是超标量和超长指令字处理器等指令多流出技术所面临最严重的硬件限制。
3. 超标量和超长指令字处理器固有的技术限制
超标量:大量硬件、设计复杂、时钟效率提高困难
VLIW:工作全部由编译器进行
现存的多数标量处理器,将静态调度和硬件的动态调度机制结合,共同决定可同时并行流出的指令数。
设计多流出处理器的主要难点:访存的开销、硬件的复杂性、编译技术的难度
4.5 超流水线处理机
- 超流水线处理机:在一个时钟周期内能够分时流出多条指令的处理机
- 超标量处理机是靠增加比较多的硬件实现的,而超流水线只需要增加少量硬件,是通过各个部分硬件的充分重叠工作来提高性能的。超标量处理机采用的是空间并行性,而超流水线处理机采用的是时间并行性。
- 对于一台每个时钟周期能流出n条指令的超流水线计算机来说,这n条指令不是同时流出的,而是每隔1/n个时钟周期流出一条指令。实际上该超流水线计算机的流水周期为1/n个时钟周期。在分解流水段时,根据实际的情况,各段的级数可以多少不一。把指令流水线级数为8或者8以上的流水线处理机称为超流水线处理机。
- 垂直的虚线表示级与级之间的界限,也即流水线寄存器所在的位置。
- 指令在IS的末尾就可以使用了
- 判断指令Cache是否命中是在RF段执行的
5. 循环展开和指令调度
5.1 循环展开和指令调度的基本方法
为了使流水线充分发挥作用应该使其满负荷工作,要求充分开发指令之间存在的并行性。增加指令间并行性最简单和最常用的方法是开发循环级并行性——循环的不同迭代之间存在的并行性。在把循环展开后,通过重命名和指令调度来开发更多的并行性。
编译器完成这种指令调度的能力受限于两个特性:
- 程序固有的指令级并行性
- 流水线功能部件的执行延迟
先尽量减少指令周期,然后把减少后的无效操作进行清理。
5.2 静态超标量处理机中的循环展开
超标量MIPS流水线的性能主要受限于整数计算和浮点计算之间的平衡问题。
例题
该种类型题首先应该考虑【指令执行状态时钟周期表】,应从下面几个方面着手:
- 【流出部分填空】时刻考虑当前指令是否存在结构冲突,即当前的保留站是否已经满了,如果保留站已经满了,当前的指令要等待保留站中流出一个指令,它才能从指令队列中流出
这里前面在保留站中已经占满了,只有前面的ADD指令流出之后当前的指令才能流出。 - 【执行阶段填空】时刻考虑当前的指令是否需要前面的指令的结果才能进行运算,也就是涉及到数据冲突的问题,需要等待写结果阶段;时刻考虑题目只给了一个浮点乘法运算部件和浮点加法运算部件,也就是说当前一条浮点乘法运算和一条浮点加法运算可以同时并行,但是同一段时钟周期内两条浮点乘法运算和两条浮点加法运算不能并行,所以当前的DIV.D指令需要在上面的MUL.D指令运行完毕后才能执行。(同一时钟周期内一个运算部件不能同时被两条指令同时使用)
3.【写结果阶段】一般写结果阶段都是直接顺着前面的执行阶段+1即可,但是有的指令要考虑数据冲突的原因要看一下是否在当前写入合适还是后延一些周期等待前面的某些操作完成
然后考虑【保留站/缓冲器状态表】:
- 【Vi部分填空】这里填入的都是可以直接计算出来的结果,注意题目中所给的寄存器和主存状态表,一般按照这个表填写内容。注意因为寄存器换名的存在,还要同时考虑当前正在运行阶段的指令的状态是啥样的,是已经过了执行阶段正在等待写结果,还是尚未进入执行阶段刚刚流出,这个Vi中存储的应该是最终计算出来的数值,比如下面:
【Q7】:这里首先MUL1的指令是寄存器F4中的内容,这时看寄存器F4的内容是否是确定的,前面有LD.D F4,0x03(R1)
而且该指令已经运行完成,所以当前F4中的内容应该是确定的,再查看R1存储的值是0xC0
,偏移值是0x03
,二者相加得到0xC3
,然后再主存中找这个位置最终对应的数值为0x06
最终Q7=0x06
【Q8】:F2中存储的数值是从0x00+R1
来的,这个结果对应表中#0xC0
的数据得到的是0x04
所以前面的rs中存储的是0x04
,后面的Q8也是同理,F0寄存器的档期啊最后结果来自SUB.D F0,F4,F2
应该是F4和F2中存储数据的差值的数值直接写到Q8的位置上计算过程如下:
LD.D F2,0x00(R1)-> F2=0x04
...
LD.D F4,0x03(R1)-> F4=0x06
...
SUB.D F0,F4,F2-> F0=0x06-0x04=0x02
所以最后的结果部分填0x02
【⚠️只要记住V的部分存储的都是最后真实的数值即可,这个数值可能是需要通过前面的指令进行计算之后得到的。】
2. 【Q部分填空】:填入的都是最早出现这个结果的保留站的名称
最后考虑【寄存器状态表】:
也就是现在正在执行的待计算得结果的指令所在的保留站的名称,比如F4正在等待ADD2的保留站的结果,所以Q9的答案是ADD2
就是前面的寄存器状态表那列填的东西变成了Label中的ROBi
;然后多了一个State和Target
(最后当前的指令结果要写入的寄存器)