赞
踩
单位时间执行的指令数目是衡量CPU的一项重要指标,为了让各个部件尽量处于工作状态,于是提出了指令流水,但是随之而来的问题就是因为程序之间的相关性,从而引起的流水线堵塞,影响了流水线性能。
为了进一步提高流水线性能,就提出了乱序执行,也就是部分程序不需按照原先的顺序执行。可以试想,若有一指令执行时间非常长,而后边执行时间短而且操作数都准备就绪的指令和本条指令之间无数据流动,那为何不让后边的提前执行以便更进一步的执行随后的工作呢。这只是一个例子,这种根据组件状态以及判断是否可提前执行的技术就是乱序执行。
- ADD R1,R2,R3
- SUB R4,R1,R5
ADD指令的写入操作,迟于SUB的读取操作,从而SUB读了错数据。而数据相关就是今天要讨论的重点。
- mov ax,2
- mov cx,11
- s:add ax,ax
- loop s
- mov ax,2
以下讨论几种乱序执行造成的数据冲突。
- SUB F0,F1,F2
- ADD F1,F4,F5 //乱序之后SUB指令的F1会读到ADD的结果
- SUB F1,F2,F3
- ADD F1,F2,F3 //乱序之后,F1存了SUB的运算结果
- SUB F1,F2,F3
- ADD F4,F1,F5 //乱序之后ADD指令读取的是SUB运算之前的F1
(这里借用一下别人的图片)
简述一下思想的话就是,待执行的指令分为三个阶段,发射,执行,写回。
还是上边的硬件结构,假设各个寄存器初始的值均为1.0,我们要执行以下代码。
- DIV F0,F1,F2 //F0 <- F1 / F2
- MUL F3,F0,F2 //F3 <- F0 * F2
- ADD F0,F1,F2 //F0 <- F1 + F2
- MUL F3,F0,F2 //F3 <- F0 * F2
1. DIV指令发射,发现F1,F2都准备好了。所以乘法保留站会更新为酱事儿的。div表示操作类型,两个0表示数据准备好了,1.0 表示操作数(其实这个过程相当于寄存器重命名了,在数据没准备好的时候“重命名”的意义会体现的更明显)。
浮点寄存器更新,F0的st标号为3,表示"目前我这个F0大家是不能读取的哈,要等3号保留站记录信号"。
2. MUL1发射,发现F0需要等待,F2准备好了。所以乘法保留站更新,3表示要等待保留站编号为3的(他上边那一条记录)结果,浮点寄存器同上。
3. ADD发射,重点来了,他发现F1,F2都准备好了而且加法操作的部件空闲,所以无需等待,可以乱序执行。加法保留栈更新,这时候发现浮点计数器中F0还在等待3的写回,这就是这个算法的灵魂所在,我要覆盖掉之前的st标号,也就是说在真正写回寄存器之前我还得按顺序写回,反正随后3还会广播通知别人更新,那我直接覆盖掉不就完事了嘛,这样就消除了WAW,WAR:
4. MUL2发射发现,F0要等待加法保留站6号保留站记录的结果,F2直接读取,另外,浮点寄存器F3的等待st标号也要更新为最新的。
5. 考虑ADD要比DIV快太多了,ADD先写回,通过数据线(红线)广播给浮点寄存器和各个保留站,“兄弟们!你们st号为6的已经被我更新了,你们可以用了!!!”
6. DIV写回,广播 “兄弟们!你们st号为3的也已经被我更新了,你们可以用了” 。
这时就是“重命名”思想体现的最明显的时候,无论是DIV指令之前的读取还是之后的读取都会得到正确的值。如果是DIV指令之前读F0,那么保留站中记录的是该寄存器准备好了。如果是DIV指令之后读F0,就需要被DIV的广播信号更新。感觉很巧妙^_^
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。