处理数据冒险的乱序执行技术

顺序执行与数据冒险

22e9e525-f1f7-4ce8-aaa1-56f385d4d3b1

dc76ac5e-5201-4077-8cb6-9107b52831a8

为讨论乱序执行技术,先引入一种超标量计算机,并先讨论其上的顺序执行,所谓顺序执行就是前面讨论的流水线的执行方式,即指令都按流水线的方向完成其各个步骤。

该机器包含R0~R7共8个指令层可见的寄存器,其所有算术指令都由3个寄存器完成(比如R0=R1+R2)。而超标量意味该机器的流水线是有“分支”的,2个分支能“同时”完成计算并写回公共的R0~R7寄存器,这种分支流水线通常是每个分支有自己独立的ALU等硬件。

该机器执行算术指令的时序是这样,如在第n周期译码,则在第n+1周期开始执行,加减在第n+2周期结束时把结果写入寄存器,乘法则要多占1个周期。比如第k周期发射2个加法指令,如寄存器不冲突,第k+2周期结束时,就分别在2个寄存器里。译码器能在1个周期里译码和发射指令,也可之后发射。

上图第二张为该计算机顺序执行若干个算术指令的例子。其中完成列表示在该周期结束时指令完成,发送列表示译码器在该周期发射指令。右边的两大列则是记分牌,是该计算机中的额外硬件。其中读取列为当前有多少个正在执行的指令引用了该寄存器中的数据。写入列为当前是否有正在执行的指令会写入该寄存器。显然记分牌将被用于处理冒险,该技术最早被CDC6600超级计算机使用。实际计算机的记分牌和流水线更复杂,还会记录ALU等功能单元的占用。这里规定了这些功能单元一直是可用的。

译码单元通过记分牌判断能否发射指令,出现相关就不能发射,相关共有3种

1) 读后写RAW:又称真相关,对应于待发射指令的输入寄存器正在被

2) 写后读WAR:又称反相关,对应于待发射指令的输出寄存器正在被

3) 写后写WAW:又称输出相关,对应于待发射指令的输出寄存器正在被

其中(1)是“真正的相关”,是前后数据依赖导致的,这种最难处理,必须等数据到达。虽然流水线会让指令实现变复杂,但指令层必须满足“依赖关系”,比如执行相邻2个指令R2=R0+R1、R4=R2+R3。第2条的R2必须是第1条结束时的结果。(2)和(3)则“不太严重”,因为没有数据依赖,更像硬件资源冲突,所以可通过暂存到其他寄存器解决。没有相关就可发射指令,比如指令1和2“同时”读R0逻辑上说的通。另外还可发现,前后两个指令没有相关时,两个指令间完全没影响,完全可并行

在多个指令间存在相关,并且是按流水的方式执行时,一定要非常小心的处理,因为指令必须严格的遵循前后的数据依赖关系。所以这里再更严谨的表述一遍:“若待发射指令与当前所有正在执行的指令都没有相关,那么该指令可以发射,其计算结果必然正确,且不会影响正在执行的指令的结果”。而通过记分牌就能判断待发射指令与当前所有正在执行的指令是否存在相关

至此,我们能够通过记分牌发现数据冒险,并可通过插入气泡解决数据冒险。但这是不够的。

 

顺序完成与精确中断

分析上图的执行流程会发现一个问题,指令2为什么在周期4才完成而不是周期3?这个问题非常重要!原因是对这台机器的指令有个“隐藏”的要求,即指令必须顺序完成!虽然这不是所以计算机都需要遵守的。这种要求的一个原因是用于支持精确中断/精确异常,也就是满足如下条件的中断:

1) 之前的指令都已提交对计算机状态的改变;

2) 之后的指令都没有改变计算机状态

CPU可能是这样实现中断的,先等之前最后一条指令完成,然后清空流水线,再记录好当前计算机的“状态”(指令层可见的寄存器),转而进入中断服务程序。可以发现在这种情况下,如果指令是乱序完成的,那最后一条指令完成时,它前面的指令可能还在流水线上,还未提交对“状态”的改变。那么中断服务程序结束后,很难恢复为中断前的状态。但若满足指令顺序完成,这种中断就满足精确中断的要求。

 

寄存器重命名

在使用寄存器重命名技术的计算机中,指令层可见寄存器(称为ISA寄存器)是逻辑寄存器,CPU用寄存器映射表记录ISA寄存器名对应的物理寄存器,这张表里的映射是可修改的,也就是说ISA寄存器的名称可被分配给不同的物理寄存器。可认为查询映射和修改映射是译码单元的行为。假设这台计算机使用寄存器重命名技术,每个指令发射前,译码单元都会找到当前对应的物理寄存器,以此发射指令。

然后看怎么通过寄存器重命名处理WAR/WAW相关。假设现在有一个待发射的指令,其与1条或多条正在执行的指令有WAR/WAW相关,这时译码单元只需在发射时刻前修改寄存器映射表,将其输出寄存器的名称指向其他“空闲”的物理寄存器。这个方案十分的巧妙,这里分情况讨论下其正确性:

1) WAR:即有正在执行的指令在该指令的输出寄存器。这些指令发射时就已经确定了当时各个寄存器的物理地址,该指令发射不影响其原本的执行;

2) WAW:即有正在执行的指令在该指令的输出寄存器。虽然该指令的发射会导致这些指令写入的结果丢失,但这些写入结果本该在该指令执行后被覆盖;

从物理寄存器角度看,当前正在执行的所有指令间都没相关,都将得到正确结果。而新发射指令如果与其中某些本有WAR/WAW相关,那么既不影响它们的计算结果,自己本身也能得到正确结果。起码保证指令顺序发射时是这样的。总之寄存器重命名能消除WAR/WAW(假设物理寄存器用不完)。

 

乱序发射

寄存器重命名能消除WAR/WAW相关带来的阻塞,但对RAW相关带来的阻塞毫无改进,虽然RAW是有“依赖关系”的真正相关,本质上更难减少阻塞,但是可以考虑让CPU在阻塞时做其他事情。所以这里在寄存器重命名的基础上继续引入乱序发射策略,将2者结合起来作为上述机器的乱序执行策略。

乱序发射的规则是这样,当译码单元判定当前指令有RAW无法发射,那其会跳过该指令,并从下个周期开始,试着发射后续指令,直到该指令可发射时才发射。并且这里放开对顺序完成的限制,允许乱序完成。注意乱序完成乱序发射不代表改变指令原本的次序隐含的数据依赖关系(原本的RAW相关),比如指令4在指令6后发射,如在指令4发射前,指令6修改了指令4的输入寄存器,那么这是不允许的。

引入乱序发射会更难维护指令的正确性,这里考虑如何解决这个问题。把需要跳过的指令计为I,最一般的情况下,I不仅和正在执行的某些指令有RAW相关,还可能和某些指令有WAR、WAW相关。而I的后续指令也可能与I有RAW、WAR、WAW相关。

假设I和正在执行的某些指令有WAR/WAW相关,可在I被跳过时做寄存器重命名,这时只剩无法解决的RAW相关。再看后续指令与I有WAR/WAW相关的情况,对后续指令做寄存器重命名的话,I在发射时可能被后续指令的寄存器重命名影响到,所以I在被跳过时就必须按当时的寄存器映射表,把指令的寄存器名“替换”为物理寄存器。最后就是后续指令与I出现RAW相关的情况,这等于回到了开始的问题。

这样就能得出解决方案,在记分牌中加两列,用于记录被跳过指令的“读取的寄存器”、“写入的寄存器”。用于让后续指令判断是否有RAW、WAR、WAW相关。另外还可以发现后续指令和被跳过指令出现WAW相关时,似乎可以直接不执行被跳过指令。总之乱序发射可减少RAW阻塞时间的浪费

这里讨论的寄存器重命名+乱序发射方案是没有给出具体实现的,一种具体实现是改进过的Tomasulo算法。Tomasulo算法启发了后来的Intel处理器的设计。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部