3. 指令级并行及其利用

404 阅读5分钟

依赖与数据冒险

(依赖=相关 != 冒险=冲突)

共有3种类型的依赖:数据依赖(data dependence)、名称依赖(name dependence)和控制依赖(control dependence)。

如果以下任意一个条件成立,则说指令j数据依赖于指令i。

  • 指令i生成的结果可能会被指令j用到
  • 指令j数据依赖于指令k,指令k数据依赖于指令i

在指令i和指令j之间存在两种类型的名称依赖。

当指令j对指令i读取的寄存器或存储地址执行写操作是,就会在指令i和指令j之间发生反依赖(antidependence)。

当指令i和指令j对同一个寄存器或存储地址执行写操作时,发生输出依赖(output dependence)。

控制依赖决定了指令i相对于分支指令的顺序,使指令i按正确的程序顺序执行,而且只会在应当执行时执行。

数据冒险可以分为3类

  • RAW——与数据依赖相对应
  • WAW——与输出依赖相对应
  • WAR——与反依赖对应

基本流水线调度和循环展开

相对于分支和开销指令而言,增加指令数量的一个简单方案是循环展开。展开就是将循环体复制多次,同时调整循环的终止代码。

用高级分支预测降低分支成本

由于需要通过分支冒险和停顿来实现控制依赖,所以分支会有损流水线性能。循环展开是减少分支冒险数量的一种方法,我们还可以通过预测分支的行为方式来降低分支造成的性能损失。

用动态调度克服数据冒险

简单流水线技术的一个主要限制是,它们使用顺序发射与执行:指令按程序顺序发射;如果一条指令停顿在流水线中,后续指令都不能执行。如果存在多个功能单元,这些单元也可能处于空闲状态。考虑以下代码:

fdiv.d  f0, f2, f4
fadd.d f10, f0, f8
fsub.d f12, f8, f14

由于fadd.d对fdiv.d的依赖性会导致流水线停顿,所以fsub.d指令不能执行;但是,fsub.d与流水线中的任何指令都没有数据依赖性。这一冒险会对性能造成限制,如果不需要以程序顺序来执行指令,就可以消除这一限制。

为了能够执行上面例子中的fsub.d,必须将发射过程分为两个部分:检查所有结构冒险和等待数据冒险的消失。因此,我们仍然使用顺序指令发射,但我们希望一条指令能够在其数据操作数可用时立即开始执行。这样的流水线实际是乱序执行(out-of-order execution),这也就意味着乱序完成(out-of-order completion)。

为了能够进行乱序执行,我们将五级简单流水线的ID流水级大体分解为以下两个阶段。

  1. 发射(issue)——指令译码,检查结构冒险
  2. 读取操作数——一直等到没有数据冒险后,然后读取操作数

在动态调度流水线中,所有指令都顺序经历发射阶段;但是,它们可能在读取操作数阶段停顿或者相互旁路,从而进入乱序执行状态。记分牌(scoreboarding)技术允许在有足够资源且不存在数据依赖时乱序执行指令。Tomasulo算法通过对寄存器进行有效的动态重命名来处理反依赖和输出依赖。此外,还可以对Tomasulo算法进行扩展,用来处理推测,这种技术通过预测一个分支的输出、执行预测目标地址的指令、在预测错误时采取纠正措施,降低控制依赖的影响。

基于硬件的推测

通过预测分支的输出,然后在假定猜测正确的前提下执行程序,可以克服控制依赖的问题。这种机制是对采用动态调度的分支预测的虽细微但很重要的扩展。具体来说,通过推测,我们提取、发射和执行指令,就好像分支预测总是正确的;而动态调度只是提取和发射这些指令。这一节研究硬件推测(hardware speculation),它延伸了动态调度的思想。

基于硬件的推测结合了3种关键思想:

  • 用动态分支预测选择要执行哪些指令
  • 利用推测,可以在解决控制依赖问题之前执行指令
  • 进行动态调度,以应对不同组合方式的基本模块调度

以多发射和静态调度来利用ILP

前面几节介绍的技术可以用来消除数据与控制停顿,使CPI达到理想值1.为了进一步提高性能,我们希望将CPI降低至小于1。 多发射处理器的目标就是允许在一个时钟周期种发射多条指令。多发射处理器主要有以下3类:

  • 静态调度超标量处理器
  • VLIW(超长指令字)处理器
  • 动态调度超标量处理器

两种超标量处理器每个时钟发射不同数目的指令。与之相对,VLIW处理器每个时钟周期发射固定数目的指令。