我正在修读计算机体系结构课程。我在另一所大学找到了这个网站,其中包含了迄今为止帮助我的笔记和视频:CS6810, Univ of Utah。我正在处理该网站上发布的一些旧的家庭作业,特别是this one。我试图理解流水线和相关概念,特别是停顿和分支延迟槽。
我现在正在查看旧作业中的第一个问题,并且不确定如何解决这些问题。
问题如下:
考虑以下代码段,其中30%的时间采用分支而不是 占70%的时间。
R1 = R2 + R3
R4 = R5 + R6
R7 = R8 + R9
如果R10 = 0,则转移到linex
R11 = R12 + R13
R14 = R11 + R15
R16 = R14 + R17
...
linex:R18 = R19 + R20
R21 = R18 + R22
R23 = R18 + R21
...
考虑一个10阶有序处理器,其中指令在第一阶段获取 阶段,分支结果经过三个阶段后才知道。估计的CPI 处理器在以下情况下(假设处理器中的所有停顿都是 分支相关和分支占所有已执行指令的15%):
在每个分支上,fetch一直停止,直到知道分支结果。
如果分支被采取,则预测每个分支都不被采用,并且错误的指令被压扁。
- 醇>
处理器有两个延迟槽,总是提取并执行分支后的两条指令,
3.1。您无法找到填写延迟位置的任何说明。
3.2。您可以在分支之前将两条指令移动到延迟槽中。
3.3。您可以在标签“linex”之后将两条指令移动到延迟槽中。
3.4。您可以在分支(在原始代码中)之后立即将一个(注意:一个,而不是两个!)指令移动到延迟槽中。
我不确定如何开始研究这个问题。我已经阅读了所有笔记并观看了该网站上的视频,并阅读了H& P书中的部分内容,但仍然对此问题感到困惑。如果有人有时间,我会很感激有人帮助我逐步解决这个问题。我只需要知道如何开始概念化答案。
答案 0 :(得分:4)
在所描述的流水线中,条件分支的方向和目标在第三个循环结束之前是不可用的,因此在分支之后的正确的下一条指令无法获取(确定)直到第四个循环的开始。 / p>
在分支之后处理指令地址延迟可用性的一种显而易见的方法就是等待。这就是设计1通过停止两个周期所做的事情(相当于获取两个不属于实际程序的无操作)。这意味着对于已采用和未采用的路径,将浪费两个周期,就像编译器插入了两个无操作指令一样。
以下是管道图(ST是一个档位,NO是无操作,XX是取消指令,UU是无用指令,I1,I2和I3是分支前的三个指令[在在填充任何延迟槽之前的原始程序顺序],BI是分支指令,I5,I6和I7是分支后的直通指令,I21,I22和I23是采用路径开始时的指令; IF是指令获取阶段,DE是解码,BR是分支解析,S1是BR之后的阶段:
Taken Not taken
IF DE BR S1 ... IF DE BR S1 ...
cycle 1 BI I3 I2 I1 BI I3 I2 I1
cycle 2 ST BI I3 I2 ST BI I3 I2
cycle 3 ST ST BI I3 ST ST BI I3
cycle 4 I21 ST ST BI I5 ST ST BI
cycle 5 I22 I21 ST ST I6 I5 ST ST
为了避免在IF阶段结束时必须检测到分支的存在,并且有时允许一些有用的工作(在未采用的情况下),而不是让硬件有效地将no-ops插入管道(即,在分支之后取消停顿)硬件可以将分支视为任何其他指令,直到在第三个流水线阶段中解析为止。这是预测未采取的所有分支。如果采用分支,则取消分支后取出的两条指令(实际上变为无操作)。这是设计2:
Taken Not taken
IF DE BR S1 ... IF DE BR S1 ...
cycle 1 BI I3 I2 I1 BI I3 I2 I1
cycle 2 I5 BI I3 I2 I5 BI I3 I2
cycle 3 I6 I5 BI I3 I6 I5 BI I3
cycle 4 I21 XX XX BI I7 I6 I5 BI
cycle 5 I22 I21 XX XX I8 I7 I6 I5
每当采取分支时,始终预测不采取分支将浪费两个周期,因此开发了第三种机制以避免这种浪费 - 延迟分支。在延迟分支中,硬件总是执行(不取消)分支后的延迟时隙指令(示例中的两条指令)。通过始终执行延迟槽指令,简化了流水线。编译器的工作是尝试用有用的指令填充这些延迟槽。
从分支之前(在没有延迟分支的程序中)获取的指令将是有用的,无论采用哪个路径(但依赖性可以阻止编译器在分支之后调度任何此类指令)。编译器可以使用来自采用或不采用路径的指令填充延迟时隙,但是这样的指令不能覆盖其他路径使用的状态(或者在路径连接之后),因为延迟时隙指令不被取消(与预测)。 (如果两个路径都加入 - 这对于if-then-else结构是常见的 - 那么延迟槽可能会从连接点填充;但是这些指令通常依赖于来自连接之前的至少一个路径的指令,哪个依赖项会阻止它们在延迟槽中使用。)如果编译器找不到有用的指令,它必须用no-op填充延迟槽。
在3.1的情况下(延迟分支设计的最坏情况),编译器找不到任何有用的指令来填充延迟槽,因此必须用no-ops填充它们:
Taken Not taken
IF DE BR S1 ... IF DE BR S1 ...
cycle 1 BI I3 I2 I1 BI I3 I2 I1
cycle 2 NO BI I3 I2 NO BI I3 I2
cycle 3 NO NO BI I3 NO NO BI I3
cycle 4 I21 NO NO BI I5 NO NO BI
cycle 5 I22 I21 NO NO I6 I5 NO NO
这相当于设计1的性能(停止两个周期)。
在案例3.2(延迟分支设计的最佳情况)中,编译器在分支之前找到两条指令来填充延迟槽:
Taken Not taken
IF DE BR S1 ... IF DE BR S1 ...
cycle 1 BI I1 ... BI I1 ...
cycle 2 I2 BI I1 ... I2 BI I1 ...
cycle 3 I3 I2 BI I1 I3 I2 BI I1
cycle 4 I21 I3 I2 BI I5 I3 I2 BI
cycle 5 I22 I21 I3 I2 I6 I5 I3 I2
在这种情况下,无论是否采用分支,所有管道槽都填充有用的指令。性能(CPI)与没有延迟分支分辨率的理想流水线相同。
在3.3的情况下,编译器使用来自采用路径的指令填充延迟槽:
Taken Not taken
IF DE BR S1 ... IF DE BR S1 ...
cycle 1 BI I3 I2 I1 BI I3 I2 I1
cycle 2 I21 BI I3 I2 I21 BI I3 I2
cycle 3 I22 I21 BI I3 I22 I21 BI I3
cycle 4 I23 I22 I21 BI I5 UU UU BI
cycle 5 I24 I23 I22 I21 I6 I5 UU UU
在未采取的路径中,I21和I22是无用的。虽然它们实际上是执行的(并且更新状态),但是在未采用的路径中(或在任何路径连接之后)不使用此状态。对于未采用的路径,就好像延迟时隙已经填充了无操作。
在3.4的情况下,编译器只能从未采用的路径中找到一条安全指令,并且必须用no-op填充另一个延迟槽:
Taken Not taken
IF DE BR S1 ... IF DE BR S1 ...
cycle 1 BI I3 I2 I1 BI I3 I2 I1
cycle 2 I5 BI I3 I2 I5 BI I3 I2
cycle 3 NO I5 BI I3 NO I5 BI I3
cycle 4 I21 NO UU BI I6 NO I5 BI
cycle 5 I22 I21 NO UU I7 I6 NO I5
对于采用的路径,执行一个无用指令和一个无操作,浪费两个周期。对于未采用的路径,执行一个no-op,浪费一个周期。
在这种情况下计算CPI的公式是:
%non_branch * CPI_non_branch + %branch * CPI_branch
CPI_branch的计算方法是:计算分支本身所用的时间(baseCPI_branch),以及分支在采用分支时所占用的浪费周期的百分比,以及分支未用于浪费的循环的次数百分比什么时候不采取。所以CPI_branch是:
baseCPI_branch + (%taken * wasted_cycles_taken) +
(%not_taken * wasted_cycles_not_taken)
在理想的标量流水线中,每条指令需要一个周期,即每个指令的周期数为1.在此示例中,非分支指令的行为就像管道是理想的一样("处理器中的所有停顿都是与分支相关的"),因此每个非分支指令的CPI为1.同样,baseCPI_branch(不包括来自摊位,no-ops等的浪费周期)为1。
根据上面的管道图,可以确定在已采用和未采用的路径中浪费的周期数。该示例给出了分支的百分比以及采取和未采用的分支百分比。
对于设计1,采取和未采用的路径都浪费了2个周期,因此CPI_branch是:
1 + (0.3 * 2) + (0.7 *2) = 3
因此总CPI为:
(0.85 * 1) + (0.15 * 3) = 1.3