文章目录
一、指令流水线(MIPS)回顾
1.1 单周期数据通路
数字逻辑基础
- 组合逻辑与时序逻辑
- 多路选择器
- 总线
- 时钟方法:边沿触发
MIPS 核心子集
- R型:add、sub、AND、OR、slt
- 访存指令:lw、sw
- 决策指令:beq、j
数据通路部件
- 时钟周期分解:
- 取指令IF
- 译码与读寄存器
- 运算EX
- 访存与分支MEM
- 写回WB
- MIPS核心子集数据通路
1.2 单周期控制单元
ALU控制线
ALU控制信号:9个(扩充jump信号为10个)
带控制的MIPS核心子集数据通路
1.3 流水线数据通路与控制
指令周期与流水级
- 单周期实现中,任一时刻只有部分硬件在运行
- 将指令执行过程分散为五个周期,每周期只执行一个阶段
- 指令1进入ID周期后,指令2可以使用IF部分的硬件
- 指令1进入EX周期后,指令2可以使用ID部分的硬件,指令3可以使用IF部分的硬件…
- 与指令周期的五个阶段相对应,把数据通路分为五个流水级,形成流水线(pipeline)。
流水线寄存器:4个
流水线控制信号
1.4 流水线冒险
结构冒险:冗余和阻塞
数据冒险
- 旁路
- R-R指令相邻
- R-R指令之间间隔一条指令
- L-R指令之间间隔一条指令
- 阻塞
- L-R指令相邻
- 其他原因阻塞
控制冒险
- 假定分支不发生——50%
- 双预测位分支预测
- 缩短分支延迟——提前分支过程
- 阻塞
二、指令级并行概念
2.1 指令级并行的概念
指令级并行 (ILP:Instruction-Level Parallelism) :指令之间存在的一种并行性,利用它,计算机可以并行执行两条或两条以上的指令。
开发ILP的方法可以分为两大类:
- 主要基于硬件的动态开发方法
- 基于软件的静态开发方法
2.2 开发指令级并行的重要性
理想CPI是衡量流水线最高性能的一个指标
实际 CPI 往往大于理想 CPI,它是理想流水线的 CPI 加上各类停顿的时钟周期数:
C
P
I
流水线
=
C
P
I
理想
+
停
顿
结构冲突
+
停
顿
数据冲突
+
停
顿
控制冲突
CPI_{流水线} = CPI_{理想} + 停顿_{结构冲突} + 停顿_{数据冲突} + 停顿_{控制冲突}
CPI流水线=CPI理想+停顿结构冲突+停顿数据冲突+停顿控制冲突
通过减少该式右边的各项,就能减少总的 CPI,从而提高IPC。IPC是Instructions Per Cycle 的缩写,其含义是每个时钟周期完
成的指令条数。它是CPI的倒数
2.3 相关与指令级并行
**相关有三种类型:**数据相关、名相关、控制相关。
举例:
数据相关
对于两条指令 i (在前,下同)和 j (在后,下同),如果下述条件之一成立,则称指令 j 与指令据相关
- 指令 j 使用指令 i 产生的结果;
- 指令 j 与指令 k 数据相关,而指令 k 又与指令 i 数据相关。
如:写后读
L.D F0, 0(R1)
ADD.D F4, F0, F2
名相关
名:指令所访问的寄存器或存储器单元的名称。
如果两条指令使用相同的名,但是它们之间并没有数据流动,则称这两条指令存在名相关
指令 j 与指令 i 之间的名相关有两种:
反相关:如果指令 j 写的名与指令 i 读的名相同,则称指令 i 和 j 发生了反相关
指令 j 写的名=指令 i 读的名
输出相关:如果指令 j 和指令 i 写相同的名,则称指令i和j发生了输出相关
指令 j 写的名=指令 i 写的名
如:读后写(反相关)
DADDU R6, R5, R2
DADDU R5, R1, R2
再如:写后写(输出相关)
DADDU R5, R3, R2
DADDU R5, R1, R2
上面两个为什么会有问题呢?可能存在后一条指令执行的比上一条指令快,然后上一条指令就会出错了
控制相关
控制相关是指由分支指令引起的相关。为了保证程序应有的执行顺序,必须严格按控制相关确定的顺序执行。
流水线冲突有三种类型:
- 结构冲突:争夺硬件资源部件造成
- 数据冲突:数据相关、名相关造成
- 控制冲突:控制相关造成
- 相关是程序固有的一种属性,它反映了程序中指令之间的相互依赖关系。
- 具体的一次相关是否会导致实际冲突的发生以及该冲突会带来多长的停顿,则是流水线的属性。
2.4 相关问题的解决方案
- 保持相关,但避免发生冲突
- 指令调度——编译器静态调度
- 通过代码变换,消除相关
- 寄存器换名——寄存器换名消除 WAR 和 WAW
三、基本指令调度及循环展开——开发指令级并行的软件方法
3.1 指令调度的基本方法
指令调度:找出不相关的指令序列,让它们在流水线上重叠并行执行
制约编译器指令调度的因素
- 程序固有的指令级并行
- 流水线功能部件的延迟
本节使用的浮点流水线的延迟:
假设采用 MIPS 的5 段整数流水线:
- 分支的延迟:1个时钟周期。
- 整数 load 指令的延迟:1个时钟周期。
- 整数运算部件是全流水或者重复设置了足够的份数。
例:对于下面的源代码,转换成 MIPS 汇编语言,在不进行指令调度和进行指令调度两种情况下,分析其代码一次循环所需的执行时间。
for (int i = 1000; i > 0; i --) x[i] = x[i] + s;
先把程序翻译成MIPS汇编语言代码
Loop:L.D F0, 0(R1) //取x[i] ADD.D F4, F0, F2 S.D F4, 0(R1) //x[i] = x[i] + s DADDIU R1, R1, #-8 //i -- BNE R1, R2, Loop //i > 0 ?
在不进行指令调度的情况下,根据表中给出的浮点流水线中指令执行的延迟,程序的实际执行情况如下:
Loop:L.D F0, 0(R1) 1 (空转) 2 ADD.D F4, F0, F2 //数据相关 3 (空转) 4 (空转) 5 S.D F4, 0(R1) //名相关(输出相关) 6 DADDIU R1, R1, #-8 7 (空转) 8 BNE R1, R2, Loop //数据相关 9 (空转) //控制相关 10
我们尝试在不改变数据流的情况下尽可能让不相关的指令重叠并行执行
指令调度后:
Loop: L.D F0, 0(R1) 1 DADDIU R1, R1, #-8 2 ADD.D F4, F0, F2 3 (空转) 4 BNE R1, R2, Loop 5 S.D F4, 8(R1) 6
例子中的问题及解决方案
- 只有L.D、ADD.D 和 S.D这3条指令是有效操作。
- 占用3个时钟周期。
- 而 DADDIU、空转和BEN这3个时钟周期都是附加的循环控制开销。
我们能否进一步改进呢?——循环展开技术
3.2 循环展开技术
循环展开技术:把循环体的代码复制多次并按顺序排列,然后相应调整循环的结束条件。
代码的多次复制给编译器进行指令调度带来了更大的空间。
我们将上面例子中的循环展开3次得到4个循环体,然后在展开后调度和不调度的情况下分析代码的性能。
Loop: L.D F0, 0(R1) 1
(空转) 2
ADD.D F4, F0, F2 3
(空转) 4
(空转) 5
S.D F4, 0(R1) 6
L.D F6, -8(R1) 7
(空转) 8
ADD.D F8, F6, F2 9
(空转) 10
(空转) 11
S.D F8, -8(R1) 12
L.D F10, -16(R1) 13
(空转) 14
ADD.D F12, F10, F2 15
(空转) 16
(空转) 17
S.D F12, -16(R1) 18
L.D F14, -24(R1) 19
(空转) 20
ADD.D F16, F14, F2 21
(空转) 22
(空转) 23
S.D F16, -24(R1) 24
DADDIUR1, R1, # -32 25
(空转) 26
BNE R1, R2, Loop 27
(空转) 28
结果分析:
-
这个循环每遍共使用了28个时钟周期。
-
有4个循环体,完成4个元素的操作。
-
平均每个元素使用28/4=7个时钟周期
-
原始循环的每个元素需要10个时钟周期。
**节省的时间:**从减少循环控制的开销中获得的。 四个循环体减少了4 * 4 - 4 = 12个时钟周期
-
在整个展开后的循环中,实际指令只有14条,其他14个周期都是空转
效率并不高
然后我们对展开后的代码进行指令调度
Loop: L.D F0, 0(R1) 1
L.D F6, -8(R1) 2
L.D F10, -16(R1) 3
L.D F14, -24(R1) 4
ADD.D F4, F0, F2 5
ADD.D F8, F6, F2 6
ADD.D F12, F10, F2 7
ADD.D F16, F14, F2 8
S.D F4, 0(R1) 9
S.D F8, -8(R1) 10
DADDIU R1, R1, # -32 12
S.D F12, 16(R1) 11
BNE R1, R2, Loop 13
S.D F16, 8(R1) 14
- 四个L.D接四个ADD,卡掉了LD - ADD的4个空转
- 四个ADD后接四个SD卡掉ADD - SD 的8个空转
- 把循环控制的指针自减提前使其与BNE之间 隔一个SD,卡掉二者之间的1个空转
- 把最后一个SD放在BNE后面,因为不影响数据流所以又卡掉控制相关的一个空转
- 一共卡掉14个空转,我们调度后的代码不存在空转了
循环展开和指令调度的注意事项
-
保证正确性
在循环展开和调度过程中尤其要注意**两个地方的正确性:**循环控制,操作数偏移量的修改。
-
注意有效性
只有能够找到不同循环体之间的无关性,才能有效地使用循环展开。
-
使用不同的寄存器
(否则可能导致新的冲突)
-
删除多余的测试指令和分支指令,并对循环结束代码和新的循环体代码进行相应的修正。
四、动态调度解决数据冒险
静态调度
- 依靠编译器对代码进行静态调度,以减少相关和冲突。
- 它不是在程序执行的过程中、而是在编译期间进行代码调度和优化。
- 通过把相关的指令拉开距离来减少可能产生的停顿。
动态调度
在程序的执行过程中,依靠专门硬件对代码进行调度,减少数据相关导致的停顿。
4.1 动态调度的基本思想
1、到目前为止我们所使用流水线的最大的局限性:指令是按序流出和按序执行的
考虑如下代码:
DIV.D F4,F0,F2
ADD.D F10,F4,F6
SUB.D F12,F6,F14
ADD.D 和 DIV.D数据相关导致需要停顿,但是SUB.D和他俩没关系也因此受阻
在我们的基本流水线中:
一旦一条指令受阻,其后的指令都将停顿
为了使上述指令序列中的SUB.D指令能继续执行下去,必须把指令流出的工作拆分为两步:
- 检测结构冲突——只要检测到没有结构冲突,就可以让指令流出。
- 等待数据冲突消失——流出后的指令一旦其操作数就绪就可以立即执行。
2、乱序执行
- 指令的执行顺序与程序顺序不相同
- 指令的完成也是乱序完成的
- 即指令的完成顺序与程序顺序不相同。
3、为了支持乱序执行,我们将5段流水线的译码阶段再分为两个阶段:
- 流出(Issue,IS):指令译码,检查是否存在结构冲突。(in-order issue)
- 读操作数(Read Operands,RO):等待数据冲突消失,然后读操作数。(out of order execution)
4、在前述5段流水线中,是不会发生WAR冲突和WAW冲突的。但乱序执行就使得它们可能发生了。
如:考虑如下代码:
DIV.D F10, F0, F2
ADD.D F10, F4, F6
SUB.D F6, F8, F14
DIV 和 ADD存在写相关:二者流出虽然只差1个时钟周期,但是ADD很可能比DIV快,所以会出现错误
ADD和SUB存在反相关:在基本流水线中,前面先读,后面再写不会有问题,但是现在指令乱序执行了,就很可能出现后面写完前面又读的情况
Tomasulo算法可以通过寄存器重命名来消除。只要让二者读的寄存器和写的寄存器不一样就消除了相关。
5、动态调度的流水线支持多条指令同时处于执行当中。
**要求:**具有多个功能部件、或者功能部件流水化、或者兼而有之。
我们假设具有多个功能部件。
6、指令乱序完成带来的最大问题:异常处理比较复杂 (了解即可)
- 动态调度的处理机要保持正确的异常行为
- 对于一条会产生异常的指令来说,只有当处理机确切地知道该指令将被执行时,才允许它产生异常。
- 即使保持了正确的异常行为,动态调度处理机仍可能发生不精确异常。
- 不精确异常:当执行指令i导致发生异常时,处理机的现场(状态)与严格按程序顺序执行时指令i的现场不同
- 精确异常:如果发生异常时,处理机的现场跟严格按程序顺序执行时指令 i 的现场相同。
记分牌算法和Tomasulo算法是两种比较典型的动态调度算法。
我们这里只介绍Tomasulo算法
4.2 Tomasulo算法
张晨曦老师用工厂加工类比Tomasulo算法简直绝绝子,B站上有相关视频,看完之后,后面Tomasulo算法的各种流程简直通透。
4.2.1 基本思想
1、核心思想
- 记录和检测指令相关,操作数一旦就绪就立即执行,把发生RAW冲突的可能性减少到最小
- 通过寄存器换名来消除WAR冲突和WAW冲突
寄存器换名举例:
考虑如下代码:
DIV.D F0,F2,F4
ADD.D F6,F0,F8
S.D F6,0(R1)
SUB.D F8,F10,F14
MUL.D F6, F10,F8
显然存在F6的输出相关和F8的反相关
消除名相关
-
引入两个临时寄存器S和T
-
把这段代码改写为:
DIV.D F0,F2,F4 ADD.D S,F0,F8 S.D S,0(R1) SUB.D T,F10,F14 MUL.D F6,F10,T
基于Tomasulo算法的MIPS处理器浮点部件的基本结构
保留站(reservation station)
保留站:每个保留站中保存一条已经流出并等待到本功能部件执行的指令。(相关信息)
保留站内包括:操作码、操作数以及用于检测和解决冲突的信息。
- 在一条指令流出到保留站的时候,如果该指令的源操作数已经在寄存器内准备就绪,则将之取到该保留站中。
- 如果操作数还没有计算出来,则在该保留站中记录将产生这个操作数的保留站的标识。
上图中:
- 浮点加法器有3个保留站:ADD1, ADD2, ADD3
- 浮点乘法器有两个保留站:MULT1, MULT2
- 每个保留站都有一个标识字段,唯一地标识了该保留站
公共数据总线CDB(一条重要的数据通路)
-
所有功能部件的计算结果都是送到CDB上,由它把这些结果直接送到各个需要该结果的地方。
-
在具有多个执行部件且采用多流出(即每个时钟周期流出多条指令)的流水线中,需要采用多条CDB
load缓冲器和store缓冲器
-
存放读/写存储器的数据或地址
-
load缓冲器的作用有3个:
-
存放用于计算有效地址的分量
-
记录正在进行的load访存,等待存储器的响应;
保存已经完成了的load的结果(即从存储器取来的数据),等待CDB传输。
-
-
store缓冲器的作用有3个:
- 存放用于计算有效地址的分量;
- 保存正在进行的store访存的目标地址,该store正在等待存储数据的到达;
- 保存该store的地址和数据,直到存储部件接收。
浮点寄存器FP
- 共有16个浮点寄存器:F0、F2、F4、……、F30
- 它们通过一对总线连接到功能部件,并通过CDB连接到store缓冲器。
指令队列
- 指令部件送来的指令放入指令队列
- 指令队列中的指令按先进先出的顺序流出
运算部件
- 浮点加法器完成加法和减法操作
- 浮点乘法器完成乘法和除法操作
在 Tomasulo 算法中,寄存器换名是通过保留站和流出逻辑来共同完成的
- 当指令流出时,如果其操作数还没有计算出来,则将该指令中相应的寄存器换名为将产生这个操作数的保留站的标识。
- 指令流出到保留站后,其操作数寄存器号或者换成了数据本身(如果该数据已经就绪),或者换成了保留站的标识,不再与寄存器有关系。
Tomasulo 算法具有以下两个特点:
- 冲突检测和执行控制是分布的
- 每个功能部件的保留站中的信息决定了什么时候指令可以在该功能部件开始执行。
- 计算结果通过CDB直接从产生它的保留站传送到所有需要它的功能部件,而不用经过寄存器。
使用 Tomasulo 算法的流水线需要3段
-
流出:从指令队列头部取出一条指令
-
如果该指令的操作所要求的保留站用空闲的,就把该指令送到该保留站(设为r)。
-
如果其操作数在寄存器中已经就绪,就将这些操作数送入保留站r
-
如果其操作数还没有就绪,就把将产生该操作数的标识送入保留站r
-
一旦被记录的保留站完成计算,它将直接把数据送给保留站r
(寄存器换名和对操作数进行缓冲,消除WAR冲突)
-
-
完成对目标寄存器的预约工作
(消除了WAW冲突)
-
如果没有空闲的保留站,指令就不能流出
出现了结构冲突
-
-
执行
- 当两个操作数都就绪后,本保留站就用相应的功能部件开始执行规定的操作
- load和store指令的执行需要两个步骤:
- 计算有效地址(需要等到基地址寄存器就绪)
- 把有效地址放入load或store缓冲器
-
写结果
- 功能部件计算完毕后,就将计算结果放到CDB上,所有等待该计算结果的寄存器和保留站(包括store缓冲器)都同时 从CDB上获得所需要的数据
保留站的7个字段
- Op:要对源操作数进行的操作
- Qj,Qk:将产生源操作数的保留站号
- 等于0表示操作数已经就绪且在Vj或Vk中,或者不需要操作数。
- Vj,Vk:源操作数的值
- 对于每个操作数来说,V和Q字段只有一个有效
- 对于load来说,Vk字段用于保存偏移量
- Busy:为“yes”表示该保留站忙
- A:仅load和store缓冲器有该字段。开始是存放指令中的立即数字段,地址计算后存放有效地址。
- Qi:寄存器状态表
- 每个寄存器在该表中有对应的一项,用于存放将把结果写入该寄存器的保留站的站号。
- 为0表示当前没有正在执行的指令要写入该寄存器,也即该寄存器中的内容就绪。
4.2.2 算法实现
示例程序的Tomasulo算法运行过程可以看张晨曦老师的教学录像,有个模拟器来着,但是没找到。
各符号意义
- **r:**分配给当前指令的保留站或者缓冲器单元编号
- **rd:**目标寄存器编号
- **rs、rt:**操作数寄存器编号
- **imm:**符号扩展后的立即数
- **RS:**保留站
- **result:**浮点部件或load缓冲器返回的结果
- **Qi:**寄存器状态表
- **Regs[]:**寄存器组
- 与 rs 对应的保留站字段:Vj,Qj
- 与 rt 对应的保留站字段:Vk,Qk
- Qi、Qj、Qk或大于0,或是一个大于0的整数
- Qi为0代表寄存器就绪
- Qj、Qk为0代表保留站或缓冲器中的Vj或Vk字段中的数据就绪
- 当他们为正整数时,表示相应的寄存器、保留站或缓冲器单元正在等待结果
符号说明举例:
-
指令流出
-
浮点运算指令
-
进入条件:有空闲保留站r if (Qi[rs] != 0) //检查rs是否就绪 RS[r].Qj = Qi[rs]; //换名 else { RS[r].Vj = Regs[rs]; //取值 RS[r].Qj = 0; //就绪 } if (Qi[rt] != 0) //检查rt是否就绪 RS[r].Qk = Qi[rt]; //换名 else { RS[r].Vk = Regs[rt]; //取值 RS[r].Qk = 0; //就绪 } RS[r].Busy = yes; //保留站忙 RS[r].Op = Op; //设置操作码 Qi[rd] = r; //目标寄存器预约
-
-
load/store指令
-
进入条件:有空闲保留站r if (Qi[rs] != 0) RS[r].Qj = Qi[rs]; //换名 else { RS[r].Vj Regs[rs]; //取值 RS[r].Qj = 0; //就绪 } RS[r].busy = yes; //忙 RS[r].A = Imm; //初始是Imm,后面与rs相加 对于load指令 Qi[rt] = r; //load完成目标寄存器预约 对于store指令 if (Qi[rt] != 0) //要存的数是否就绪 RS[r].Qk = Qi[rt]; //换名 else { RS[r].Vk = Regs[rt]; //取值 RS[r].Qk = 0; //就绪 }
-
-
-
执行
-
浮点操作指令
- 进入条件:(RS[r].Qj = 0) 且 (RS[r].Qk = 0)
- **操作和状态表内容修改:**进行计算,产生结果
-
load/store指令
-
进入条件:(RS[r].Qj = 0) 且 r成为 load/store 缓冲队列的头部
-
操作和状态表内容修改:
RS[r].A = RS[r].Vj + RS[r].A;
对于load指令,完成有效地址计算后,还需要从MEM[RS[r].A]中取数据
-
-
-
写结果
-
浮点运算指令和load指令
进入条件:保留站r执行结束,且CDB就绪
操作和状态表内容修改:
∀ ( i f ( Q i [ x ] = = r ) ) \forall (if (Qi[x] == r)) ∀(if(Qi[x]==r))
Regs[x] = result;
Qi[x] = 0;
∀ ( i f ( R S [ x ] . Q j = = r ) ) \forall (if (RS[x].Qj == r)) ∀(if(RS[x].Qj==r))
RS[x].Vj = result;
RS[x].Qj = 0;
∀ ( i f ( R S [ x ] . Q k = = r ) ) \forall (if (RS[x].Qk == r)) ∀(if(RS[x].Qk==r))
RS[x].Vk = result;
RS[x].Qk = 0;
RS[r].busy = no;
-
store指令
进入条件:保留站r执行结束,且RS[r].Qk = 0
Mem[RS[r].A] = RS[r].Vk;
RS[r].Busy = no;
-
五、动态调度解决控制冒险
Tomasulo算法只能处理结构冲突和数据冲突,不能处理异常和控制冲突,指令队列里有分支指令,分支指令之后的指令不能流出,直到确定分支是否成功。
动态分支预测:在程序运行时,根据分支指令过去的表现来预测其将来的行为
分支预测的有效性取决于:
- 预测的准确性
- 预测的正确和不正确两种情况下的分支开销
- 决定分支开销的因素:
- 流水线的结构
- 预测的方法
- 预测错误时的恢复策略等。
- 决定分支开销的因素:
需要解决的关键问题:
- 如何记录分支的历史信息
- 如何根据这些信息来预测分支的去向(甚至取到指令)
在预测错误时,要作废已经预取和分析的指令,恢复现场,并从另一条分支路径重新取指令。
5.1 采用分支历史表 BHT
分支历史表BHT(Branch History Table)
- 最简单的动态分支预测方法
- 用BHT来记录分支指令最近一次或几次的执行情况(成功还是失败),并据此进行预测。
1、只有1个预测位的分支预测
记录分支指令最近一次的历史,BHT中只需要1位二进制位。
2、采用两位二进制位来记录历史
- 提高预测的准确度
- 研究结果表明:两位分支预测的性能与n位(n > 2)分支预测的性能差不多
两位分支预测的状态转化如下所示:
两位分支预测中的操作有两个步骤:
- 分支预测:
- 当分支指令到达译码段(ID)时,根据从BHT读出的信息进行分支预测。
- 若预测正确,就继续处理后续的指令,流水线没有断流。否则就要作废已经预取和分析的指令,恢复现场,并从另一条分支路径重新取地址。
- 状态修改。
BHT方法只有在以下情况才有用:
判定分支是否成功的时间大于确定分支目标地址所需的时间。
之间的5段经典流水线:由于判定分支是否成功和计算分支目标地址都是在ID段完成,所以BHT方法不会给该流水线带来好处。
BHT可以跟分支指令一起存放在指令Cache中,也可以用一块专门的硬件来实现。
5.2 采用分支目标缓冲器BTB
在高性能流水线中,我们准确的预测分支还不够,还要能快速地提供足够的指令流。
对于经典五段流水线,BHT方法在ID段完成分支预测和分支目标地址的计算,如果我们能够提前一拍,在IF段就完成,那么分支开销就能降低到0。
而分支目标缓冲器(Branch Target Buffer,BTB),或者分支目标Cache(branch Target Cache)
BTB的结构如下:
关于BTB:
-
是一张用专门的硬件实现的一张表格
-
表格中的每一项至少有两个字段:
-
执行过的成功分支指令的地址
(作为该表的匹配标识)
-
预测的分支目标地址
-
那么采用BTB后,流水线处理分支指令的步骤如下:
采用BTB后的延迟情况如下:
指令在BTB中? | 预测 | 实际情况 | 延迟周期 |
---|---|---|---|
是 | 成功 | 成功 | 0 |
是 | 成功 | 失败 | 2 |
不是 | 成功 | 2 | |
不是 | 不成功 | 0 |
对于上表的解释:
- 指令在BTB中且实际情况失败:那么需要一个时钟周期删除BTB对应项,花费一个时钟周期,更新BTB时要暂停取指令操作(避免造成影响),后面又需要一个时钟周期重新取指令
- 指令不在BTB中且实际情况成功:一个时钟周期写BTB,同样要暂停取指令,所以还需要一个时钟周期重新取指令
例:假设有一条长流水线,仅仅对条件转移指令使用分支目标缓冲。假设分支预测错误的开销为4个时钟周期,缓冲不命中的开销为3个时钟周期。假设:命中率为90%,预测精度为90%,分支频率为15%,没有分支的基本 CPI 为1。
(1) 求程序执行的 CPI。
(2) 相对于采用固定的2个时钟周期延迟的分支处理,哪种方法程序执行速度更快?
( 1 ) C P I = 基本 C P I + 分支额外开销 = 1 + 0.15 × ( 0.9 × 0.1 × 4 + 0.1 × 3 ) = 1.099 ( 2 ) C P I 2 = 1 + 0.15 × 2 = 1.3 显然第一种快 \begin{align} & (1) CPI = 基本CPI + 分支额外开销 \\ &= 1 + 0.15\times(0.9\times0.1\times4+0.1\times3)\\ &= 1.099 \\ & (2) CPI_{2} = 1 + 0.15 \times2 = 1.3 \\ & 显然第一种快 \end{align} (1)CPI=基本CPI+分支额外开销=1+0.15×(0.9×0.1×4+0.1×3)=1.099(2)CPI2=1+0.15×2=1.3显然第一种快
BTB的另一种形式
在分支目标缓冲器中存放一条或者多条分支目标处的指令。
-
有三个潜在的好处:
-
更快地获得分支目标处的指令;
-
可以一次提供分支目标处的多条指令,这对于多流出处理器是很有必要的
-
使我们可以进行称为**分支折叠(branch folding)**的优化。实现零延迟无条件分支,甚至有时还可以做到零延迟条件分支。
-
5.3 基于硬件的前瞻执行
前瞻执行(speculation)的基本思想
- 对分支指令的结果进行猜测,并假设这个猜测总是对的,然后按这个猜测结果继续取、流出和执行后续的指令。
- 只是执行指令的结果不是写回到寄存器或存储器,而是写入一个称为**再定序缓冲器ROB(ReOrder Buffer)**中。
- 等到相应的指令得到“确认”(commit)(即确实是应该执行的)之后才将结果写入寄存器或存储器。
基于硬件的前瞻执行结合了3种思想
- 动态分支预测。用来选择后续执行的指令。
- 在控制相关的结果尚未出来之前,前瞻地执行后续指令。
- 用动态调度对基本块的各种组合进行跨基本块的调度。
实现:对Tomasulo算法加以扩充,就可以实现前瞻执行。
Tomasulo算法的写结果和指令给完成加以区分,分成两个不同的段:
-
写结果段
- 把前瞻执行的结果写到CDB中
- 通过CDB在指令之间传送结果,供需要用到这些结果的指令使用。
-
指令确认
在分支指令的结果出来后,对相应的指令的前瞻执行给予确认。
- 如果前面的猜测是对的,把在ROB中的结果写到寄存器或存储器。
- 如果发现前面对分支指令的猜测是错的,那就不予以确认,并从那条分支指令的另一条路径开始重新执行。
实现前瞻的关键思想:
**允许指令乱序执行,但必须顺序确认。**在指令被确认之前不允许对它进行不可恢复的操作。
支持前瞻执行的浮点部件的结构
ROB中的每一项由以下4个字段组成:
-
指令类型
指出该指令是分支指令、store指令或寄存器操作指令。
-
目标地址
给出指令执行结果应写入的目标寄存器号(如果是load和ALU指令)或存储器单元的地址(如果是stroe指令)
-
数据值字段
用来保存指令前瞻执行的结果,知道指令得到确认
-
就绪字段
指出指令是否已经完成执行并且数据已就绪
Tomasulo算法中保留站的换名功能是由ROB来完成的。
采用前瞻执行后的指令执行步骤
流出
- 从浮点指令队列头部取一条指令
- 如果有空闲的保留站(记为r)且有空闲的ROB项(设为b),就流出该指令,并把相应信息写入保留站r和ROB项b。
- 如果保留站或ROB全满,便停止流出指令,直到它们都有空闲的项。
执行
- 如果操作数尚未就绪,就等待,并不断地监测CDB(检测RAW冲突)
- 当两个操作数都已在保留站中就绪后,就可以执行该指令的操作。
写结果
- 当结果产生后,将该结果连同本指令在流出段分配到的ROB项编号放到CDB上,经CDB写到ROB以及所有等待该结果的保留站。
- 释放产生该结果的保留站。
- store指令在本阶段完成,其操作为:
- 如果要写入存储器的数据已经就绪,就把该数据写入分配给该store指令的ROB项。
- 否则,就检测CDB,直到那个数据在CDB上播送出来,才将之写入分配给该store指令的ROB项。
确认
对分支指令、store指令以及其它指令的处理不同:
-
其它指令(除分支指令和store指令)
当该指令到达ROB头部而且其结果已经就绪时,就把该结果写入该指令的目的寄存器,并从ROB中删除该指令。
-
store指令
处理和上面类似,只是它把结果写入存储器。
-
分支指令
- 当预测错误的分支指令到达ROB队列的头部时,情况ROB并从分支指令的另一个分支重新开始执行。(错误的前瞻执行)
- 当预测正确的分支指令到达ROB头部时,该指令执行完毕。
例:假设浮点功能部件的延迟时间为:加法2个时钟周期,乘法10个时钟周期,除法40个时钟周期。对于下面的代码段,给出当指令MUL.D即将确认时的状态表内容。
L.D F6, 34(R2) L.D F2, 45(R3) MUL.D F0, F2, F4 SUB.D F8, F6, F2 DIV.D F10, F0, F6 ADD.D F6, F8, F2
前瞻执行优缺点:
- 通过ROB实现了指令的顺序完成
- 能够实现精确异常
- 很容易地推广到整数寄存器和整数功能单元上。
- 主要缺点:所需的硬件太复杂。
六、复习题
1、请说明流水线中有哪三种相关?分别会引起哪种流水线中的冲突(冒险)?
- 数据相关——数据冲突
- 如写后读
- 名相关——数据冲突
- 如写后写、读后写
- 控制相关——控制冲突
2、请说明什么是静态调度?什么是动态调度?动态调度的优点是什么?
**静态调度:**依靠编译器对代码进行静态调度,以减少相关和数据冲突。它不是在程序执行过程中,而是在编译期间对代码进行调度和优化,对相关的处理方式在程序执行过程中始终不发生变化。
通过把相关的指令拉开距离来减少可能产生的停顿。
**动态调度:**是硬件调度指令,在代码执行的过程中进行调度。
动态调度优点:
- 能够处理一些在编译期间不明的相关(如涉及到存储器访问的相关),并简化了编译器。
- 能够使本来是面向某一流水线优化编译的代码在其他的流水线(动态调度)上也能高效地执行。
**缺点:**以硬件复杂性的显著增加为代价。
3、请简述Tomasulo算法的基本思想。
核心思想
- 记录和检测指令相关,操作数一旦就绪就立即执行,把发生RAW冲突的可能性减少到最小(通过CDB)。
- 通过寄存器换名来消除WAR冲突和WAW冲突
4、说明什么是动态分支预测及其优点
静态分支预测技术:进行的操作事先预定好,与分支的实际执行情况无关。
动态分支预测技术:
- 在程序执行时根据分支执行过去的表现预测其将来的行为(如果分支行为发生变化,预测结果跟着改变,此外有更好的预测准确度和适应性)。
- 在程序执行时,根据每一条转移指令过去的转移历史记录预测下一次转移的方向。
优点:根据程序的执行情况动态地改变转移的预测方向,有更好的准确度和适应性。
5、简述分支历史表BHT的基本思想
**分支历史表(Branch History Table)**是最简单的动态分支预测方式,用BHT来记录分支指令最近一次或几次的执行情况(成功或不成功),并据此进行预测。
6、请简述分支目标缓冲器BTB的基本思想
分支目标缓冲器BTB
将分支成功的分支指令的地址和它的分支目标地址都放到一个缓冲区中保存起来,缓冲区以分支指令的地址作为标识。
这个缓冲区就是分支目标缓冲器(BTB,Branch-Target Buffer)。
7、请简述基于硬件的前瞻算法基本思想
基于硬件的前瞻执行结合了三种思想:
- 动态分支预测,用来选择后续执行的指令。
- 在控制相关的结果尚未出来之前,前瞻地执行后续指令、乱序。
- 用动态调度对基本块的各种组合进行跨基本块的调度。