计算机系统结构 第三章:指令级并行

计算机系统结构 第三章:指令级并行

本章知识结构图:

指令级并行
指令流水线复习
指令级并行概念及基本编译技术
动态调度解决数据冒险
动态调度解决控制冒险
多指令流出技术

指令流水线复习

I型,R型和J型指令结构

五段流水线的功能
在这里插入图片描述
流水线冒险

结构冒险:因缺乏硬件支持导致指令不能在预定的时钟周期内执行。
数据冒险:因无法提供指令执行所需数据导致指令不能在预定的时钟周期内执行。
控制冒险:即分支冒险,因所取到的指令不是所需要的而导致指令不能在预定的时钟周期内执行。

指令级并行概念及基本编译技术

概念

指令级并行ILP概念:是指存在于指令一级即指令间的并行性, 主要是指机器语言一级, 如存储器访问指令、整型指令、浮点指令之间的并行性等。

指令级并行主要特点:是并行性由处理器硬件和编译程序自动识别和利用, 不需要程序员对顺序程序作任何修改。正是由于这一优点, 使得它的发展与处理器的发展紧密相连。

重要性

  • 理想CPI(无冲突)是衡量流水线最高性能的一个指标
  • 实际CPI往往远大于理想CPI,它是理想流水线的CPI加上各类停顿的时钟周期数:
    C P I 流 水 线 = C P I 理 想 + 停 顿 结 构 冲 突 + 停 顿 数 据 冲 突 + 停 顿 控 制 冲 突 CPI流水线 = CPI理想 + 停顿结构冲突 + 停顿数据冲突 + 停顿控制冲突 CPI线=CPI+++
  • 减少等式右端的各项就减少了总的CPI,从而提高IPC.

需要解决的具体问题

相关:

  • 两条指令之间存在某种依赖关系
  • 分类:数据相关,名相关,控制相关

流水线冲突

  • 对于具体的流水线来说,由于相关等原因的存在,使得指令流中的下一条指令不能在指定的时钟周期执行。
  • 流水线冲突有三种类型:结构冲突、数据冲突、控制冲突

小结与比较

  • 相关是程序固有的一种属性,它反映了程序中指令之间的相互依赖关系。
  • 具体的一次相关是否会导致实际冲突的发生以及该冲突会带来多长的停顿,则是流水线的属性。(相关是冲突的主要原因)

两类解决方案

保持相关,但避免发生冲突

  • 指令调度 – 编译器静态调度

通过代码变换,消除相关

  • 寄存器重命名 – 寄存器换名消除WAR和WAW

开放指令级并行的方法

  • 基于软件的静态开发方法
  • 基于硬件的动态开发方法
  • 硬件+软件技术

只有硬件技术和软件技术互相配合,才能够最大限度地挖掘出程序中存在的指令级并行。

基本编译技术——循环展开和指令调度

  • 充分开发指令之间存在的并行性,找出不相关的指令序列,让它们在流水线上重叠并行执行。
  • 增加指令间并行性最简单和最常用的方法
    • 开发循环级并行性——循环的不同迭代之间存在的并行性。
    • 在把循环展开后,通过(寄存器)重命名和指令调度来开发更多的并行性。
  • 编译器完成这种指令调度的能力受限于两个特性:
    • 程序固有的指令级并行性;
    • 流水线功能部件的执行延迟。

循环展开和指令调度时要注意以下几个方面:

  • 保证正确性。在循环展开和调度过程中尤其要注意两个地方的正确性:循环控制,操作数偏移量的修改。
  • 注意有效性。只有能够找到不同循环体之间的无关性,才能有效地使用循环展开。
  • 使用不同的寄存器。(否则可能导致新的冲突)
  • 删除多余的测试指令和分支指令,并对循环结束代码和新的循环体代码进行相应的修正。
  • 注意对存储器数据的相关性分析例如:对于load指令和store指令,如果它们在不同的循环迭代中访问的存储器地址是不同的,它们就是相互独立的,可以相互对调。
  • 注意新的相关性。由于原循环不同次的迭代在展开后都到了同一次循环体中,因此可能带来新的相关性。

动态调度解决数据冒险

基本思想

相关的3种类型

  • 数据相关:数据冒险之写后读;解决方案:旁路,编译器指令调度
  • 名相关:反相关(数据冒险之读后写),输出相关(数据冒险之写后写)
  • 控制相关:控制冒险;解决方案:预测分支失败/成功,延迟分支

静态调度 V.S. 动态调度

  • 静态调度:是依靠编译器对代码进行调度,也就是在代码被执行之前进行调度;通过把相关的指令拉开距离来减少可能产生的停顿。
  • 动态调度:在程序的执行过程中,依靠专门硬件对代码进行调度,减少数据相关导致的停顿。

指令顺序执行 V.S. 指令乱序执行

  • 指令顺序执行:指令放入流水线的顺序和指令完成的顺序一致;
  • 指令乱序执行:指令放入流水线的顺序和指令完成的顺序不一致,也就是说有些指令进入流水线后被阻塞的,而在其后进入流水线的指令先完成了。

动态调度例子中,将ID段又分成了两个阶段:

  • 流出(Issue,IS)阶段:指令译码,检查是否存在结构冲突。
  • 读操作数(Read Operands,RO)阶段:检测数据冲突,如果没有,继续执行;如果有,等待数据冲突消失,然后读操作数。

另一个例子中,介绍了寄存器换名技术:
指令1 DIV.D F4,F0,F2
指令2 SUB.D F10,F4,F6
指令3 ADD.D F6,F12,F14

如果在指令2停顿周期中先执行指令3,会发生写后读冲突,利用寄存器换名技术,可以变成:
指令1 DIV.D F4,F0,F2
指令2 SUB.D F10,F4,S
指令3 ADD.D F6,F12,F14
消除了名相关。

小结

  • 相对于静态指令调度 ,动态指令调度是在指令的执行过程中进行调度,使得无关的指令得以先执行,减少阻塞;
  • 能够处理一些在编译时情况不明的相关(如存储器访问的相关);
  • 能够使本来是面向某一流水线优化编译的代码在其他的流水线(动态调度)上也能高效地执行;
  • 动态指令调度将会引起指令乱序执行,因此,使用换名技术消除名相关(包括反相关和输出相关);
  • 指令乱序完成使得异常处理困难;
  • 以硬件复杂性的显著增加为代价。

Tomasulo算法

核心思想
  • 记录和检测指令相关,操作数一旦就绪就立即执行,把发生RAW冲突的可能性减少到最小;
  • 通过寄存器换名来消除WAR冲突和WAW冲突。
Tomasulo的基本结构

主要部件:

  • 指令队列
  • 保留站
  • Store/load缓冲器
  • 公共数据总线CDB
  • 运算部件
  • 存储部件
  • 浮点寄存器

在这里插入图片描述
保留站

  • 每个保留站中保存一条已经流出并等待到本功能部件执行的指令(相关信息)。
  • 每个保留站包括操作码、操作数以及用于检测和解决冲突的信息。
    • 在一条指令流出送到保留站的时候,如果该指令的源操作数已经在寄存器中就绪,则将操作数取到该保留站中。
    • 如果操作数还没有计算出来,则在该保留站中记录即将产生这个操作数的保留站的标识。
  • 浮点加法器有3个保留站:ADD1,ADD2,ADD3
  • 浮点乘法器有2个保留站:MULT1,MULT2
  • 每个保留站都有一个标识字段,唯一地标识了该保留站。

公共数据总线CDB(一条重要的数据通路)

  • 所有功能部件的计算结果都是送到CDB上,由它把这些结果直接送到(播送到)各个需要该结果的地方。
  • 在具有多个执行部件且采用多流出(即每个时钟周期流出多条指令)的流水线中,需要采用多条-CDB。
  • 从存储器读取的数据也送到CDB。
  • CDB连接到除了load缓冲器以外的所有部件的入口。
  • 浮点寄存器通过一对总线连接到功能部件,并通过CDB连接到store缓冲器的入口。

load(读)缓冲器的作用有3个

  • 存放用于计算有效地址的分量;
  • 记录正在进行的load访存,等待存储器的响应;
  • 保存已经完成了的load的结果(即从存储器取来的数据),等待CDB传输。

store(写)缓冲器的作用有3个:

  • 存放用于计算有效地址的分量;
  • 保存正在进行的store访存的目标地址,该store正在等待存储数据的到达;
  • 保存该store的地址和数据,直到存储部件接收。

浮点寄存器FP

  • 共有16个浮点寄存器:F0,F2,F4,…,F30。
  • 它们通过一对总线连接到功能部件,并通过CDB连接到store缓冲器。

指令队列

  • 指令部件送来的指令放入指令队列
  • 指令队列中的指令按先进先出的顺序流出

运算部件

  • 浮点加法器完成加法和减法操作
  • 浮点乘法器完成乘法和除法操作

在Tomasulo算法中,寄存器换名是通过保留站和流出逻辑来共同完成的。

  • 当指令流出时,如果其操作数还没有计算出来,则将该指令中相应的寄存器号 换名 为将产生这个操作数的保留站的标识。
  • 指令流出到保留站后,其操作数寄存器号或者换成了数据本身(如果该数据已经就绪),或者换成了保留站的标识,不再与寄存器有关系。这样 后面指令对寄存器的写入操作就不可能产生WAR冲突了
指令执行步骤

流出

  • 从指令队列头部取一条指令,如果操作所要求的的保留站空闲,送入保留站(设为r);
  • 如果其操作数在寄存器就绪,将操作数送入保留站r,否则将产生该操作数的保留站的标识送入保留站r;(进行了寄存器换名和操作数缓冲,消除了WAR冲突)
  • 完成对目的寄存器的预约工作,将之设置为接收保留站r的结果。(相当于提前完成了写操作,如果多条指令写同一个结果寄存器,由于指令顺序流出,最后留下的预约结果肯定是最后一条指令的,消除了WAW冲突)

执行

  • 如果某个操作数还没有计算出来,保留站将监视CDB,等待结果,当两个操作数都就绪后,保留站就用相应的功能部件开始执行指令规定的操作;(靠推迟执行的方法解决RAW冲突,由于结果数据是从保留站直接送到需要它的地方而不经过寄存器,所以这最大限度减少了RAW冲突的影响)
  • 如果出现多条指令在同一时钟周期就绪的情况,不同的功能部件可以并行执行,但在一个功能部件内部,就绪的多条指令只能逐条处理;
  • load和store指令的执行需要2个步骤:计算有效地址(要等到基地址寄存器就绪)和把有效地址放入load和store缓冲器。load缓冲器中的load指令的执行条件是存储器部件就绪。而store缓冲器中的store指令在执行前必须等到要存入存储器的数据到达。通过按顺序进行有效地址计算来保证程序顺序,这有助于避免访问存储器的冲突。

写结果

  • 功能部件计算完毕后,就将计算结果放到CDB上,所有等待该计算结果的寄存器和保留站(包括store寄存器)都同时从CDB上获得所需要的数据。

Tomasulo算法具有两个主要的优点:

  • ①冲突检测和指令执行控制是分布的
    • 每个功能部件的保留站中的信息决定了什么时候指令可以在该功能部件开始执行。
    • 计算结果通过CDB直接从产生它的保留站传送到所有需要它的功能部件,而不用经过寄存器。
  • ②消除了WAW冲突和WAR冲突导致的停顿
    • 使用保留站进行寄存器换名,并且操作数一旦就绪就将之放入保留站。
Tomasulo具体算法详细解读

各符号的意义

  • r:分配给当前指令的保留站或者缓冲器单元编号;
  • rs、rt、rd:rd目的寄存器编号,rs、rt操作数寄存器编号;
  • imm:符号扩展后的立即数;
  • RS:保留站;
  • result:浮点部件或load缓冲器返回的结果;
  • Qi:寄存器状态表;
  • Regs[ ]:寄存器组;
  • Op:当前指令的操作码
  • 与rs对应的保留站字段:Vj,Qj
  • 与rt对应的保留站字段:Vk,Qk
  • Load指令:rt是保存所取数据的寄存器号
  • Store指令:rt是保存所要存储的数据的寄存器号
  • Qi、Qj、Qk的内容或者为0,或者是一个大于0的整数。
    • Qi为0表示相应寄存器中的数据就绪。
    • Qj、Qk为0表示保留站或缓冲器单元中的Vj或Vk字段中的数据就绪。
    • 当它们为正整数时,表示相应的寄存器、保留站或缓冲器单元正在等待结果。

指令流出

①浮点运算指令
进入条件:有空闲保留站(设为r)
操作和状态表内容修改:

if (Qi[rs] = 0// 检测第一操作数是否就绪
  { RS[r].Vj ← Regs[rs]// 第一操作数就绪。把寄存器rs 	
                            // 中的操作数取到当前保留站的Vj。
    RS[r].Qj ← 0 }// 置Qj为0,表示当前保留站的Vj       
                            // 中的操作数就绪。
else	     // 第一操作数没有就绪
 { RS[r].Qj ← Qi[rs] }	      // 进行寄存器换名,即把将产生该  
             // 操作数的保留站的编号放入当前保留站的Qj。
if (Qi[rt] = 0// 检测第二操作数是否就绪
 { RS[r].Vk ← Regs[rt]// 第二操作数就绪。把寄存器rt中的
                           	// 操作数取到当前保留站的Vk。
    RS[r].Qk ← 0 }           	// 置Qk为0,表示当前保留站的Vk中的
                           	// 操作数就绪。
else                              //第二操作数没有就绪
 { RS[r].Qk ← Qi[rt] }     //进行寄存器换名,即把将产生该操作
                        // 数的保留站的编号放入当前保留站的Qk。           
RS[r].Busy ← yes;	//置当前保留站为“忙”
RS[r].Op ← Op;  	//设置操作码
Qi[rd] ← r;	// 把当前保留站的编号r放入rd所对应
                      // 的寄存器状态表项,以便rd将来接收结果。          

② load和store指令
进入条件:缓冲器有空闲单元(设为r)
操作和状态表内容修改:

if (Qi[rs] = 0// 检测第一操作数是否就绪
 {RS[r].Vj ← Regs[rs]// 第一操作数就绪,把寄存器rs中的
                         	// 操作数取到当前缓冲器单元的Vj
   RS[r].Qj ← 0 }// 置Qj为0,表示当前缓冲器单元的Vj
                         	// 中的操作数就绪。
 else	// 第一操作数没有就绪
 {RS[r].Qj ← Qi[rs] }	// 进行寄存器换名,即把将产生该   
              // 操作数的保留站的编号存入当前缓冲器单元的Qj。
RS[r].Busy ← yes;	// 置当前缓冲器单元为“忙”
RS[r].A ← Imm;	// 把符号位扩展后的偏移量放入
                        	// 当前缓冲器单元的A                               
对于load指令:
Qi[rt] ← r;	// 把当前缓冲器单元的编号r放入
                   // load指令的目标寄存器rt所对应的寄存器
                   // 状态表项,以便rt将来接收所取的数据。
对于store指令:			
if (Qi[rt] = 0// 检测要存储的数据是否就绪
 {RS[r].Vk ← Regs[rt]// 该数据就绪,把它从寄存器rt取到
                        	// store缓冲器单元的Vk
    RS[r].Qk ← 0 }// 置Qk为0,表示当前缓冲器单元的Vk
                        	// 中的数据就绪。
else              // 该数据没有就绪
  {RS[r].Qk ← Qi[rt] }	// 进行寄存器换名,即把将产生该数
                     //据的保留站的编号放入当前缓冲器单元的Qk。              

执行

① 浮点操作指令
进入条件:(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指令,在完成有效地址计算后,还要进行:(load指令两个任务:计算访存地址,访存)
从Mem[RS[r].A]读取数据; //从存储器中读取数据(store指令在该阶段,只需要第一个操作数就绪)

写结果
① 浮点运算指令和load指令
进入条件:保留站r执行结束,且CDB就绪。
操作和状态表内容修改:

对所有x (if(Qi[x] = r)	// 对于任何一个正在等该结果
                          	// 的浮点寄存器x
    { Regs[x] ← result;	// 向该寄存器写入结果
        Qi[x]0 }// 把该寄存器的状态置为数据就绪
对所有x (if(RS[x].Qj = r)	// 对于任何一个正在等该结果
                           	// 作为第一操作数的保留站x
   {RS[x].Vj ← result;	// 向该保留站的Vj写入结果
      RS[x].Qj ← 0 }// 置Qj为0,表示该保留站的
                           	// Vj中的操作数就绪 
对所有x (if(RS[x].Qk = r)  	// 对于任何一个正在等该结果作为
                              	// 第二操作数的保留站x
   {RS[x].Vk ← result;	// 向该保留站的Vk写入结果
     RS[x].Qk ← 0 }// 置Qk为0,表示该保留站的Vk中的
                            	// 操作数就绪。
RS[r].Busy ← no;        // 释放当前保留站,将之置为空闲状态。

② store指令
进入条件:保留站r执行结束,且RS[r].Qk = 0 (要存储的数据已就绪,该阶段需要第二个操作数就绪)
操作和状态表内容修改:

Mem[RS[r].A] ← RS[r].Vk	// 数据写入存储器,地址由store
                            	// 缓冲器单元的A字段给出。
RS[r].Busy ← no;    //释放当前缓冲器单元,将之置为空闲状态。 
Tomasulo算法总结

在这里插入图片描述
其中,store指令的访存就是它的写结果阶段。
需要注意的地方:

  • 用硬件的思维理解硬件的算法;
  • 一个clock,也就是一个时钟周期,打开的是一个存储设备到另一个存储设备之间的数据通路;
  • 对于任意一条指令,任意一个阶段,能否进入(或者说开始执行它要执行的任务)的唯一审视标准是:进入条件是否满足。
Tomasulo的要点总结
  • ① 指令按照程序原有的顺序进入指令队列,并按照原有的顺序流出指令队列,但是并不一定按照原有的顺序执行。
  • ② 操作数未就绪的指令在保留站中等待,操作数就绪的指令可以先被执行。
  • ③ 通过寄存器换名技术消除了名相关,从而解决了读后写和写后写冲突。换了什么名呢?把流出到保留站中的指令的操作数寄存器换成了操作数,或者产生该操作数的保留站号。
  • ④ 指令能够流出、执行和写结果这三步的进入条件请看详细的算法,但是还有一个隐含条件,即没有结构冲突。
  • ⑤ Tomasulo算法只能处理结构冲突和数据冲突,不能处理异常和控制冲突。指令队列里有分支指令,分支指令之后的指令不能流出,直到确定分支是否成功。
  • ⑥ 在算法中,有两个地方容易忽视,需要格外注意:一个是从Load/store指令要按照FIFO次序访存;另一个是,store指令执行阶段和写结果阶段进入的条件。

动态调度解决控制冒险

动态调度解决控制冒险
分支历史表
分支目标缓冲器
基于硬件的前瞻执行

动态分支预测技术:

  • 通过硬件技术,在程序执行时根据每一条转移指令过去的转移历史记录来预测下一次转移的方向。通过提前预测分支方向,减少或消除控制相关导致的流水线停顿。

优点:

  • 根据程序的执行过程动态地改变转移的预测方向,因此有更好的准确度和适应性。
  • 程序每次执行时,可能预测的分支方向与前次相同或不同。

从简单到复杂的动态转移预测技术如下:

  • 分支预测缓存器(BHT)
  • 分支目标缓冲器(BTB)
  • 基于硬件的前瞻执行(ROB)

以上预测机制的性能随着复杂性与硬件的增加而有效地提高。

  1. 动态分支预测技术和静态分支预测技术的区别
    静态分支预测技术所进行的操作事先预定好的 ,与分支的实际执行情况无关;
    动态分支预测技术的方法在程序运行时根据分支执行过去的表现预测其将来的行为(如果分支行为发生了变化,预测结果也跟着改变,此外有更好的预测准确度和适应性)。
  2. 什么是“分支指令过去的表现”?
    就是记录分支的历史信息,在预测错误时,要作废已经预取和分析的指令,恢复现场,为了恢复现场,需要在执行预测的目标指令之前将现场保存起来。
  3. 分支预测的有效性取决于:
  • 预测的准确性
  • 预测正确和不正确两种情况下的分支开销
  • 决定分支开销的因素:
    • 流水线的结构(5段流水,分支处理由MEM段提到ID)
    • 预测的方法
    • 预测错误时的恢复策略等
  1. 采用动态分支预测技术的目的
  • 预测分支是否成功
  • 尽快找到分支目标地址(或指令)
    (避免控制相关造成流水线停顿)
  1. 需要解决的关键问题
  • 如何记录分支的历史信息;
  • 如何根据这些分支的历史信息来预测分支的去向(甚至取到指令)。

分支历史表 BHT

  1. 分支历史表BHT(Branch History Table)或分支预测缓冲器(Branch Prediciton Buffer)
  • 最简单的动态分支预测方法。
  • 用BHT来记录分支指令最近一次或几次的执行情况(成功或不成功),并据此进行预测。
  1. 只有1个预测位的分支预测缓冲
  • 记录分支指令最近一次的历史,BHT中只需要1位二进制位。(最简单)
    “0”记录分支不成功
    “1”记录分支成功
    遇见1,预测成功。实际成功则保持1,实际失败置0
    遇见0,预测失败。实际失败则保持0,实际成功置1
    在这里插入图片描述
  1. 采用两位二进制位来记录历史
    在这里插入图片描述
    研究结果表明:两位分支预测的性能与n位(n>2)分支预测的性能差不多。

  2. BHT方法只在以下情况下才有用:

  • 判定分支是否成功所需的时间大于确定分支目标地址所需的时间。
  • 前述5段经典流水线:由于判定分支是否成功和计算分支目标地址都是在ID段完成,所以BHT方法不会给该流水线带来好处。
  • BHT可以跟分支指令一起存放在指令Cache中,也可以用一个专门的硬件来实现。

分支目标缓冲器 BTB

在高性能流水线中,特别是多流出的处理机中,只准确地预测分支还不够,还要能够提供足够的指令流。许多现代的处理器要求每个时钟周期能提供4~8条指令。这需要尽早知道分支是否成功、尽早知道分支目标地址、尽早获得分支目标指令。

方法:分支目标缓冲

  • 将分支成功的分支指令的地址和它的分支目标地址都放到一个缓冲区中保存起来,缓冲区以分支指令的地址作为标识。

BTB表结构

多指令流出技术

多指令流出技术
超标量处理机
超长指令字处理机
静态超标量
动态超标量
  • 0
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值