了解暂停和分支延迟槽



我正在上一门关于计算机体系结构的课程。我从另一所大学找到了这个网站,它有笔记和视频,到目前为止对我有帮助:犹他大学CS6810。我正在浏览那个网站上发布的一些旧的家庭作业,尤其是这一份。我正在努力理解流水线和相关概念,特别是失速和分支延迟槽。

我现在正在看旧作业中的第一个问题,不确定如何做这些问题。

问题如下:

考虑以下代码段,其中分支占用了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级的处理器,其中指令是在第一个处理器中提取的阶段,并且分支结果在三个阶段之后已知。估计以下情况下的处理器(假设处理器中的所有暂停分支相关和分支占所有执行指令的15%):

  1. 在每个分支上,获取都会被暂停,直到知道分支结果为止。

  2. 每个分支都被预测为不执行,如果执行了分支,则错误提取的指令将被压扁。

  3. 处理器有两个延迟槽,分支之后的两条指令总是被提取和执行,并且

    3.1.您找不到任何填充延迟槽的说明。

    3.2.您可以在分支之前将两条指令移动到延迟槽中。

    3.3.您可以将标签"linex"后的两条指令移动到延迟槽中。

    3.4.您可以在分支(在原始代码中)之后立即将一条(注意:一条,而不是两条!)指令移动到延迟槽中。

我甚至不确定如何开始考虑这个问题。我已经阅读了该网站上的所有笔记和视频,并阅读了H&P书,但我仍然对这个问题感到困惑。如果有人有时间,我会感谢有人帮我解决这个问题。我只需要知道如何开始将答案概念化。

在所描述的流水线中,条件分支的方向和目标直到第三个周期结束才可用,因此在第四个周期开始之前,分支之后的正确下一条指令无法提取(当然)。

设计1

处理分支后指令地址延迟可用性的一种明显方法就是等待。这就是设计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

设计2

为了避免必须在IF阶段结束时检测分支的存在,并允许有时(在未采取的情况下)完成一些有用的工作,而不是让硬件有效地在流水线中不插入任何操作(即,在分支之后暂停获取),硬件可以将分支视为任何其他指令,直到它在第三流水线阶段中被解决。这是在预测所有分支未被占用。如果执行了分支,那么在该分支之后提取的两条指令将被取消(实际上变为无操作)。这是设计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

设计3

每次执行分支时,总是预测不执行的分支都会浪费两个周期,因此开发了第三种机制来避免这种浪费——延迟分支。在延迟分支中,硬件总是在分支之后执行(不取消)延迟槽指令(示例中有两条指令)。通过始终执行延迟槽指令,简化了流水线。编译器的工作是尝试用有用的指令来填充这些延迟槽。

无论采用哪条路径,从分支之前(在没有延迟分支的程序中)获取的指令都是有用的(但依赖关系可能会阻止编译器在分支之后调度任何此类指令)。编译器可以用取自或未取自路径的指令填充延迟槽,但这样的指令不能覆盖其他路径使用的状态(或在路径连接之后),因为延迟槽指令不会被取消(与预测不同)。(如果两条路径都连接在一起(If-then-else构造很常见),那么延迟槽可能会从连接点开始填充;但这类指令通常依赖于连接前至少一条路径中的指令,这种依赖性会阻止它们在延迟槽中使用。)如果编译器找不到有用的指令,它必须用no-op填充延迟槽。

在情况3.1(延迟分支设计的最坏情况)中,编译器找不到任何有用的指令来填充延迟槽,因此必须在没有操作的情况下填充它们:

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操作,浪费一个周期。

计算CPI

在这种情况下,计算CPI的公式是:

%non_branch * CPI_non_branch + %branch * CPI_branch

CPI_branch的计算方法是考虑分支本身所花费的时间(基本CPI_branc),以及在执行时使用浪费周期执行分支的次数百分比,以及在不执行时不使用浪费周期的分支未执行次数百分比。所以CPI_branch是:

baseCPI_branch + (%taken * wasted_cycles_taken) + 
(%not_taken * wasted_cycles_not_taken)

在理想标量流水线中,每条指令占用一个周期,即每条指令的周期数为1。在本例中,非分支指令的行为就像管道是理想的("处理器中的所有暂停都与分支相关"),因此每个非分支指令都有一个CPI为1。同样,baseCPI_branch(不包括来自暂停、无操作等的浪费周期)为1。

根据上面的管线图,可以确定在已采取和未采取路径中浪费的循环数。该示例给出了分支的百分比以及已执行和未执行的分支的百分比。

对于设计1,采用和未采用路径都浪费2个周期,因此CPI_branch为:

1 + (0.3 * 2) + (0.7 *2) = 3

因此,总CPI为:

(0.85 * 1) + (0.15 * 3) = 1.3

相关内容

  • 没有找到相关文章

最新更新