深刻浅出计算机组成原理学习笔记:第二十三讲

1、引子

一、解决不一样指令之间的数据依赖问题。

上一讲,我为你讲解告终构冒险和数据冒险,以及应对这两种冒险的两个解决方案。一种方案是增长资源,经过添加指令缓存和数据缓存,让咱们对于指令和数据的访问能够同时进行。
这个办法帮助CPU解决了取指令和访问数据之间的资源冲突。另外一种方案是直接进行等待。经过插入NOP这样的无效指令,等待以前的指令完成。这样咱们就能解决不一样指令之间的数据依赖问题java

二、上一讲的这两种方案这两种方案都有点儿笨。

着急的人,看完上一讲的这两种方案,可能已经要跳起来问了:“这也能算解决方案么?”的确,这两种方案都有点儿笨。缓存

第一种解决方案,比如是在软件开发的过程当中,发现效率不够,因而研发负责人说:“咱们须要双倍的人手和研发资源。”而第二种解决方案,比如你在提需求的时候,研发负责人告诉你说:“来不及作,你只能等
咱们需求排期。” 你应该很清楚地知道,“堆资源”和“等排期”这样的解决方案,并不会真的提升咱们的效率,只是避免冲突的无奈之举。学习

那针对流水线冒险的问题,咱们有没有更高级或者更高效的解决方案呢?既不用简单花钱加硬件电路这样“堆资源”,也不是纯粹等待以前的任务完成这样“等排期”spa

答案固然是有的。这一讲,咱们就来看看计算机组成原理中,一个更加精巧的解决方案,操做数前推翻译

2、NOP操做和指令对齐


要想理解操做数前推技术,咱们先来回顾一下,第5讲讲过的,MIPS体系结构下的R、I、J三类指令,以及第20讲里的五级流水线“取指令(IF)-指令译码(ID)-指令执行(EX)-内存访问(MEM)-数据写回
(WB) ”。我把对应的图片放进来了,你能够看一下。若是印象不深,建议你先回到这两节去复习一下,再来看今天的内容3d

在MIPS的体系结构下,不一样类型的指令,会在流水线的不一样阶段进行不一样的操做。blog

咱们以MIPS的LOAD,这样从内存里读取数据到寄存器的指令为例,来仔细看看,它须要经历的5个完整的流水线。STORE这样从寄存器往内存里写数据的指令,不须要有写回寄存器的操做,
也就是没有数据写回的流水线阶段。至于像ADD和SUB这样的加减法指令,全部操做都在寄存器完成,因此没有实际的内存访问(MEM)操做。
事件

 

 


有些指令没有对应的流水线阶段,可是咱们并不能跳过对应的阶段直接执行下一阶段。否则,若是咱们前后执行一条LOAD指令和一条ADD指令,就会发生LOAD指令的WB阶段和ADD指令的WB阶段,
在同一个时钟周期发生。这样,至关于触发了一个结构冒险事件,产生了资源竞争。
图片

因此,在实践当中,各个指令不须要的阶段,并不会直接跳过,而是会运行一次NOP操做。经过插入一个NOP操做,咱们可使后一条指令的每个Stage,必定不和前一条指令的同Stage在一个时钟周期执行。
这样,就不会发生前后两个指令,在同一时钟周期竞争相同的资源,产生结构冒险了。内存

 

 

3、流水线里的接力赛:操做数前推

经过NOP操做进行对齐,咱们在流水线里,就不会遇到资源竞争产生的结构冒险问题了。除了能够解决结构冒险以外,这个NOP操做,也是咱们以前讲的流水线停顿插入的对应操做。
可是,插入过多的NOP操做,意味着咱们的CPU老是在空转,干吃饭不干活。那么,咱们有没有什么办法,尽可能少插入一些NOP操做呢?不要着急,
下面咱们就以两条前后发生的ADD指令做为例子,看看能不能找到一些好的解决方案。

add $t0, $s2,$s1
add $s2, $s1,$t0

这两条指令很简单。
1. 第一条指令,把 s1 和 s2 寄存器里面的数据相加,存入到 t0 这个寄存器里面。
2. 第二条指令,把 s1 和 t0 寄存器里面的数据相加,存入到 s2 这个寄存器里面。

由于后一条的 add 指令,依赖寄存器 t0 里的值。而 t0 里面的值,又来自于前一条指令的计算结果。因此后一条指令,须要等待前一条指令的数据写回阶段完成以后,才能执行。
就像上一讲里讲的那样,咱们遇到了一个数据依赖类型的冒险。因而,咱们就不得不经过流水线停顿来解决这个冒险问题。咱们要在第二条指令的译码阶段以后,插入对应的NOP指令,
直到前一天指令的数据写回完成以后,才能继续执行。这样的方案,虽然解决了数据冒险的问题,可是也浪费了两个时钟周期。咱们的第2条指令,其实就是多花了2个时钟周期,运行了两次空转的NOP操做。

 

 

不过,其实咱们第二条指令的执行,未必要等待第一条指令写回完成,才能进行。若是咱们第一条指令的执行结果,可以直接传输给第二条指令的执行阶段,做为输入,那咱们的第二条指令,
就不用再从寄存器里面,把数据再单独读出来一次,才来执行代码。

咱们彻底能够在第一条指令的执行阶段完成以后,直接将结果数据传输给到下一条指令的ALU。而后,下一条指令不须要再插入两个NOP阶段,就能够继续正常走到执行阶段。

 

 

这样的解决方案,咱们就叫做 操做数前推(Operand Forwarding),或者操做数旁路(OperandBypassing)。其实我以为,更合适的名字应该叫 操做数转发。这里的Forward,
其实就是咱们写Email时的“转发”(Forward)的意思。不过现有的经典教材的中文翻译通常都叫“前推”,咱们也就不去纠正这的“转发”(Forward)的意思。不过现有的经典教材的中文翻译通常都叫“前推”,咱们也就不去纠正这个说法了,你明白这个意思就好。

转发,实际上是这个技术的 逻辑含义,也就是在第1条指令的执行结果,直接“转发”给了第2条指令的ALU做为输入。另一个名字,旁路(Bypassing),则是这个技术的 硬件含义。为了可以实现这里的“转发”,
咱们在CPU的硬件里面,须要再单独拉一根信号传输的线路出来,使得ALU的计算结果,可以从新回到ALU的输入里来。这样的一条线路,就是咱们的“旁路”。它越过(Bypass)了写入寄存器,再从寄存器读出的过程,也为咱们节省了2个时钟周期。

操做数前推的解决方案不但能够单独使用,还能够和流水线冒泡一块儿使用。有的时候,虽然咱们能够把操做数转发到下一条指令,可是下一条指令仍然须要停顿一个时钟周期。好比说,咱们先去执行一条LOAD指令,再去执行ADD指令。LOAD指令在访存阶段才能把数据读取出来,因此下一条指令的执行阶段,须要在访存阶段完成以后,才能进行。

总的来讲,操做数前推的解决方案,比流水线停顿更进了一步。

流水线停顿的方案,有点儿像游泳比赛的接力方式。下一名运动员,须要在前一个运动员游玩了全程以后,触碰到了游泳池壁才能出发。
而操做数前推,就好像短跑接力赛。后一个运动员能够提早抢跑,而前一个运动员会多跑一段主动把交接棒传递给他。

4、总结延伸

这一讲,我给你介绍了一个更加高级,也更加复杂的解决数据冒险问题方案,就是操做数前推,或者叫操做数旁路。

操做数前推,就是经过在硬件层面制造一条旁路,让一条指令的计算结果,能够直接传输给下一条指令,而再也不须要“指令1写回寄存器,指令2再读取寄存器“这样画蛇添足的操做。
这样直接传输带来的好处就是,后面的指令能够减小,甚至消除本来须要经过流水线停顿,才能解决的数据冒险问题。

这个前推的解决方案,不只能够单独使用,还能够和前面讲解过的流水线冒泡结合在一块儿使用。由于有些时候,咱们的操做数前推并不能减小全部“冒泡”,只能去掉其中的一部分。
咱们仍然须要经过插入一些“气泡”来解决冒险问题。

经过操做数前推,咱们进一步提高了CPU的运行效率。那么,咱们是否是还能找到别的办法,进一步地减小浪费呢?毕竟,看到如今,咱们仍然少不了要插入不少NOP的“气泡”。那就请你继续坚持学习下去。下一讲,咱们来看看,CPU是怎么经过乱序执行,进一步减小“气泡”的。

相关文章
相关标签/搜索