目录
一、概述
二、ROB
2.1 ROB结构
2.2 Tomasulo with ROB
2.2.1 issue
2.2.2 dispatch
2.2.3 broadcast
2.2.4 commit
2.2.5 引入rob后对比
三、其他
3.1 Unified Reservation Station
3.2 Terminology Confusion
四、load & store
2.1 tomasulo中load/store的顺序执行
2.2 现代计算机上如何实现load/store的乱序执行
2.2.1 LSQ结构
2.2.2 LSQ如何工作
2.2.3 LSQ, ROB, RS比较
2.2.4一个问题
体系结构之二的延续,因为二文章太长了,其他的部分在这里描述
在tomasulo的乱序执行过程中,我们考虑两种情况:
可以通过ReOrder Buffer解决这个问题:
ROB可以记录程序的指令顺序并保存执行结果直到可以安全写入。
ROB按照program order记录指令,ROB entry由三个部分组成:
ROB还有两个指针commit和issue,如果你了解过intel网络控制器,可以知道这两个指针和队列头尾指针类似(RDH,RDT),就是个环形队列,issue是头,指示下一条issue指令在ROB存放的位置,commit是尾,指示下一条需要提交的指令,这样commit->issue就表示issue但尚未提交,issue->commit表示ROB空闲的entry
引入ROB后,tomasulo执行的流程有一些变化,为了对比,下面以列表的形式展现,同时给出示意图,以一条指令执行过程说明,执行前,如下图这样:
Tomasulo | Tomasulo with ROB |
1. 从IQ中取指 | 从IQ中取指 |
2. Lookup RAT 如果RAT中对应的寄存器有值存在,使用RAT记录的值 如果RAT中对应的寄存器没有值,使用register file中的寄存器值 |
Lookup RAT |
3. 获取空闲的RS entry 如果RS中空闲的entry,将指令放入RS中 如果RS中没有空闲entry,要等待RS空闲 |
获取空闲的RS entry 获取commit指向的ROB entry 如果资源busy等待 |
4. 标记(tag) 目的寄存器 在RAT中将destination register指向RS entry |
标记(tag) 目的寄存器
在RAT中将destination register指向ROB entry |
和之前相比,用rob entry来代替rs entry 存储结果:
dispatch执行步骤与之前基本类似,区别在于这个阶段会free RS entry,这是因为RS记录register name的用途被ROB代替了,RS只负责在操作数准备好了dispatch到执行单元去执行,在这之后这个RS entry就没有用了,这样可以更快的为issue提供资源。
Tomasulo | Tomasulo with ROB |
1)将 rs tag和result放在总线上 |
将 rob tag和result放在总线上 |
2)结果写入RF |
无,结果不写入RF,而写入rob entry |
3) 更新RAT,如果RAT没有对应的entry说明结果已经写入RF了 |
无,不更新RAT,因为RAT指向rob entry |
4)释放对应的RS entry——修改invalid bit | 无 |
简单来说,最后的写入工作由ROB负责了,所以broadcast功能减少了,马上就可以看到,增加了commit步骤写入。broadcast执行结果示意如下:
commit的步骤是:
1)等待commit指向的指令完成,done置位
2)结果由ROB entry写入RF
3) 更新RAT
4) 释放对应的ROB entry
很明显,将原来broadcast的工作转移到commit来做
2.26
在paper中经常使用术语: Issue, Dispatch, Commit,处理器设计人员经常有不同的称呼:
目前为止,对不同的相关,我们的解决方法如下:
tomasulo算法的load/store操作,都是in-order的,我们知道load/store也有类似RAW,WAW,WAR的数据相关,那么load/store必须按照program order执行吗,是否可以用类似我们解决Register dependencies 的方法让load/store也进行乱序执行从而提高流水线执行效率呢。
memory write在commit阶段发生,因为一旦memory写回就无法挽回了,假如因为预测错误导致store指令先执行,这是无法恢复的。所以将store操作delay到commit阶段。而load指令本身需要尽快读取,因为由于cache miss,load操作经常会耗费大量的时间,需要尽快获取。而load指令没有store指令那么多限制,即使读错了,再读一遍就好了。
另一方面,很多load结果操作依赖store操作,既然store操作在commit阶段才写入,那么又如何让load尽快读取到数据呢?cpu设计者引入了一个称为LSQ(load store queue)的结构,使load能尽快获取store的结果。
LSQ和ROB类似之处在于,二者指令都是按照program order存储,在commit完成
load和store指令按照顺序进入LSQ,当load指令进入LSQ时,使用load的地址和该指令之前的store指令的地址比较,如果匹配上了,就无需再从memory中取值了,称为StoretoLoad Forwarding。当然,一种可能的情况是之前相关的store指令还没有addr,那么这个时候load指令该怎么做呢?
OOO Load/Store Execution
OOO Load/Store execution occurs when loads go to memory as soon as the address is available. Sometimes this results in stale data being used and then it needs to be recovered.
issue |
|
非load/store指令:
|
executing |
|
load操作还要将结果进行广播 |
Commit |
|
store操作还需要将结果写入内存 |
困惑已久,既然指令时顺序发射,乱序执行,顺序提交,对memory操作来说为什么还会有所谓memory barrier保证memory order呢?在wangqi的大作Cache Memory中:
现代处理器在 Commit 最后的执行结果时大多都采用 In-order 方式,这也保证了指令在
经过 Out-of-Oder 的流水线后,程序员看到的最终结果与程序应有的顺序一致。多数程序员
被这一假象迷惑,认为 CPU 的乱序执行仅与硬件流水线相关,并不会影响软件程序。
事实并非如此。 微架构为了实现乱序执行,有些指令,比如存储器读指令, 可能会提前
执行,而后因为种种原因,如分支预测失败, 可能会被迫重新执行。虽然乱序流水线可以保
证最后的结果与程序期待的结果一致,但是无法完全抹去这条本不该执行的指令在流水线中,
在存储器子系统中留下的执行痕迹
他在文中举的例子我也一并列举:
产生的根源是由于load speculation,
Time | C1 | C2 |
t0 | read A1 (得到旧值) | |
t1 | write A1 | |
t2 | increase A2 | |
t3 | check A2 |
假设t0时刻,C2 check A2指令 cache miss, read A1 进行load speculation, 此时得到的是A1的旧值
在t1时刻C1更新A1,并在t2更新A2
在t2时刻C2读取A2,发现其更新了,该条件下应该去执行read A1,误认为此次load speculation是成功的,但是A1的值是旧值!
所以在这种情况下要使用memory barrier