1.46k likes | 1.74k Views
第四章 流水线技术. 4.1 指令级并行的概念 4.2 指令的动态调度 4.3 转移预测技术 4.4 多发射技术 4.5 向 指令级并行的支持与限制 4.6 Intel Pentium 4 实例分析. 4.1 指令级并行的概念. 指令级并行的概念. 几乎所有的处理机都利用流水线来使指令重叠并行执行,以达到提高性能的目的。这种指令之间存在的潜在并行性称为 指令级并行。 ( ILP : Instruction-Level Parallelism ). 4.1.1 指令级并行. 1 、流水线处理机的实际 CPI
E N D
第四章 流水线技术 4.1 指令级并行的概念 4.2 指令的动态调度 4.3 转移预测技术 4.4 多发射技术 4.5 向指令级并行的支持与限制 4.6 Intel Pentium 4 实例分析
4.1 指令级并行的概念 指令级并行的概念 几乎所有的处理机都利用流水线来使指令重叠并行执行,以达到提高性能的目的。这种指令之间存在的潜在并行性称为指令级并行。 (ILP:Instruction-Level Parallelism)
4.1.1 指令级并行 1、流水线处理机的实际CPI • 理想流水线的CPI加上各类停顿的时钟周期数: CPI流水线 = CPI理想 + 停顿结构冲突 + 停顿数据冲突 + 停顿控制冲突 • 理想CPI是衡量流水线最高性能的一个指标。 • IPC:Instructions Per Cycle • (每个时钟周期完成的指令条数) 2、基本程序块 • 基本程序块:一段除了入口和出口以外不包含其他分支的线性代码段。 • 程序平均每5~7条指令就会有一个分支。
3、循环级并行:使一个循环中的不同循环体并行执行。3、循环级并行:使一个循环中的不同循环体并行执行。 • 开发循环体中存在的并行性 • 最常见、最基本 • 是指令级并行研究的重点之一 • 例如,考虑下述语句: for (i=1; i<=500; i=i+1) a[i]=a[i]+s; • 每一次循环都可以与其他的循环重叠并行执行; • 在每一次循环的内部,却没有任何的并行性。
4、最基本的开发循环级并行的技术 • 循环展开(loop unrolling)技术 • 采用向量指令和向量数据表示
4.1.2 相关性对指令级并行的影响 本章使用的浮点流水线的延迟
例4-1 对于下面的源代码, for (i=1; i<=1000; i++) x[i] = x[i] + s; • (Ⅰ)转换成DLX汇编语言,在不进行指令调度和进行指令调度两种情况下,分析代码一次循环的执行时间。 • (Ⅱ)编译过程进行分析,来仔细考察换名的过程。 • 备注:本章使用的浮点流水线的延迟如上表所示
解: (Ⅰ) (1) 变量分配寄存器 整数寄存器R1:循环计数器,初值为向量 中最高端地址元素的地址。 浮点寄存器F2:保存常数S。 假定最低端元素的地址为8。 (2) DLX汇编语言后的程序 Loop: LD F0,0(R1) ADDD F4,F0,F2 SD 0(R1),F4 SUBI R1,R1,#8 BNEZ R1,Loop
(3) 程序执行的实际时钟 • 根据上表中给出的的延迟,实际时钟如下: 指令流出时钟 Loop: LD F0 , 0(R1) 1 (空转) 2 ADDD F4 , F0 , F2 3 (空转) 4 (空转) 5 SD 0(R1) , F4 6 SUBI R1 , R1 , #8 7 (空转) 8 BNEZ R1 , Loop 9 (空转) 10 • 每个元素的操作需要10个时钟周期,其中5个是空转周期。
(4) 指令调度以后,程序的执行情况 • SD放在分支指令的分支延迟槽中 • 对存储器地址偏移量进行调整 指令流出时钟 Loop: LD F0 , 0(R1) 1 SUBI R1 , R1 , #8 2 ADDD F4 , F0 , F2 3 (空转) 4 BNEZ R1 , Loop 5 SD 8(R1) , F4 6 • 一个元素的操作时间从10个时钟周期减少到6个 5个周期是有指令执行的,1个空转周期。
(Ⅱ) (1) 首先,仅仅去除4遍循环体中的分支指令,得到以下由17条指令构成的指令序列,如下所示: Loop: LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 BNEZ R1 , Loop
(2) 编译器可以通过对相关链上存储器访问偏移量的直接调整,将前3条SUBI指令消除掉,从而得到下面一个14条指令构成的指令序列,如下所示: Loop: LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 LD F0 , -8(R1) ADD.D F4 , F0 , F2 SD -8(R1) , F4 LD F0 , -16(R1) ADD.D F4 , F0 , F2 SD -16(R1) , F4 LD F0 , -24(R1) ADD.D F4 , F0 , F2 SD -24(R1) , F4 SUBI R1 , R1 , #32 BNEZ R1 , Loop
(3) 通过寄存器换名,消除名相关。得到下面的指令序列,如下所示: Loop: LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 LD F6 , -8(R1) ADD.D F8 , F6 , F2 SD -8(R1) , F8 LD F10 , -16(R1) ADD.D F12 , F10 , F2 SD -16(R1) , F12 LD F14 , -24(R1) ADD.D F16 , F14 , F2 SD -24(R1) , F16 SUBI R1 , R1 , #32 BNEZ R1 , Loop
再来看一个控制相关的例子。 典型的程序结构是“if-then”结构。 if p1{ S1; }; S; if p2{ S2; }; P1控制相关S1,P2控制相关S2,而不是S1。
控制相关会带来两方面的限制: (1)控制相关于一个分支的指令不能被移到分支之前执行。例如if…else程序中,then后面的语不句能移到if之前执行 (2)没有控制相关于一个分支的指令不能移到该分支指令之后,从而受这个分支控制,例如if…then程序中,if前的指令不能移到then部分执行。
再考察例4-1,假设循环展开时,循环控制分支指令没有去除,则指令序列如下所示: loop: LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 BEQZ R1 , Exit LD F0 , 0(R1) DD F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 BEQZ R1 , Exit LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 BEQZ R1 , Exit LD F0 , 0(R1) ADD.D F4 , F0 , F2 SD 0(R1) , F4 SUBI R1 , R1 , #8 BNEZ R1 , Loop Exit:
由于上述指令段中BEQZ R1 , Exit,BEQZ R1 , Exit,BEQZ R1 , Exit三条分支指令的存在,引起控制相关,导致其后的4条指令不能跨越分支指令进行调度,即不同循环遍次里的指令不能够跨越循环遍次进行调度。
4.1.3支持指令级并行的基本编译技术 1.检测并提高循环级并行 2.循环展开技术 3.全局指令调度
1.检测并提高循环级并行 循环级并行是指在源代码或靠近源代码的层次进行并行分析,而指令级并行则是对经过编译技术产生的指令进行并行分析。 循环级并行分析集中分析某一个循环体中的数据存取是否涉及前一个循环体中的数据。这种相关叫做体间相关。
看下面的程序: for (i=1000;i>0;i=i+1) x[i]=x[i]+s; 在这个循环程序中,用到了两次x[i],它们之间即存在数据相关,但这是循环体内的相关而不是体间相关。在相邻的两次对i的引用上,存在体间相关,但这种相关包含规约变量,很容易识别和消除。
循环体间相关常常以重现的形式出现的: for (i=2;1<=100;i=i+1) Y[i]=Y[i-1]+Y[i]; 重现是对一个变量赋予前面一个循环体中一个变量的值,而且往往是最近的一个循环体中的变量
检测这种循环依赖关系有两点很重要的原因:一是有些系统结构(如向量计算机)可以专门支持执行这种循环结构的程序;二是有些存在循环依赖关系的程序也可以获得很高的并行度。 for (i=6;1<=100;i=i+1) Y[i]=Y[i-5]+Y[i]; 在第i次循环中,循环引用了第i-5个元素,因此称该循环的相关距离是5。许多有相邻体间相关的循环,它们的相关距离是1。相关距离越大,通过展开循环可以获得的并行度就越高。
2.循环展开技术 循环展开技术是利用多次复制循环体并相应调整展开后的指令和循环结束条件,增加有效操作时间与控制操作时间的比率。 这种技术也给编译器进行指令调度带来了更大的空间 例4-2:将例4-1中的循环展开成3次得到4个循环体,再对展开后的指令序列在不调度和调度两种情况下,分析代码的性能。
解:假定R1的初值为32的倍数,即循环次数为4的倍数。解:假定R1的初值为32的倍数,即循环次数为4的倍数。 寄存器分配如下: (展开后的循环体内不重复使用寄存器。) F0、F4:用于展开后的第1个循环体; F2:保存常数; F6和F8:用于展开后的第2个循环体; F10和F12:用于第3个循环体; F14和F16:用于第4个循环体。
(1) 展开后没有调度的代码 流出时钟 ADDD F12,F10,F2 15 (空转) 16 (空转) 17 SD -16(R1),F12 18 LD F14,-24(R1) 19 (空转) 20 ADDD F16,F14,F2 21 (空转) 22 (空转) 23 SD -24(R1),F16 24 SUBI R1,R1,#32 25 (空转) 26 BNEZ R1,Loop 27 (空转) 28 流出时钟 Loop: LD F0,0(R1) 1 (空转) 2 ADDD F4,F0,F2 3 (空转) 4 (空转) 5 SD 0(R1),F4 6 LD F6,-8(R1) 7 (空转) 8 ADDD F8,F6,F2 9 (空转) 10 (空转) 11 SD -8(R1),F8 12 LD F10,-16(R1) 13 (空转) 14
结果分析: • 这个循环每遍共使用了28个时钟周期 • 有4个循环体,完成4个元素的操作 平均每个元素使用28/4=7个时钟周期 • 原始循环的每个元素需要10个时钟周期 节省的时间:从减少循环控制的开销中获得的 • 在整个展开后的循环中,实际指令只有14条,其它13个周期都是空转。 效率并不高
(2) 对指令序列进行优化调度 指令流出时钟 Loop: LD F0,0(R1) 1 LD F6,-8(R1) 2 LD F10,-16(R1) 3 LD F14,-24(R1) 4 ADDD F4,F0,F2 5 ADDD F8,F6,F2 6 ADDD F12,F10,F2 7 ADDD F16,F14,F2 8 SD 0(R1),F4 9 SD -8(R1),F8 10 SUBI R1,R1,#32 12 SD 16(R1),F12 11 BNEZ R1,Loop 13 SD 8(R1),F16 14
结果分析: • 没有数据相关引起的空转等待 • 整个循环仅仅使用了14个时钟周期 • 平均每个元素的操作使用14/4=3.5个时钟周期 • 循环展开和指令调度可以有效地提高循环级并行性。 • 这种循环级并行性的提高实际是通过实现指令级并行来达到的。 • 可以使用编译器来完成,也可以通过硬件来完成。
循环展开和指令调度时要注意的问题 (1) 保证正确性 (2) 注意有效性 (3) 使用不同的寄存器 (4) 尽可能减少循环控制中的测试指令和分支指令 (5) 注意对存储器数据的相关性分析 (6) 注意新的相关性
3.全局指令调度 全局指令调度实在保证不违背数据相关和控制相关的情况下,吧代码尽可能压缩成几条指令。 循环分支指令引起的控制相关可以通过展开解决。 全局指令调度可以解决另一类由条件分支引起的控制相关。 由于跨越分支移动代码经常会改变这些指令的执行频率,所以全局指令调度的有效性依赖于对不同路径上指令执行频率的估计。 全局指令调度并不保证能提高执行速度,但如果预测信息准确,编译器就可以进一步决定上述移动会不会提高运行速度。
编译器如何来保证B和C的移动不会影响数据流呢?编译器如何来保证B和C的移动不会影响数据流呢? 先给出与图4-6流程图相对应的一段典型代码序列。 假设A、B和C分别存入寄存器R1、R2和R3。 LD R4, O(R1) LD R5, O(R2) DADDU R4, R4,R4,R5 SD R4, O(R1) ….. BNEZ R4,elsepart ….. SD …, O(R2) …. JUMP join elsepart: … X … join: … SD …, O(R3)
首先考虑把对B的赋值移至BNEZ指令之前的情况。令if语句前最后一个对B赋值的指令为i。如果B在被赋值前被引用,令引用指令为j。如果存在指令j,则移动B将会改变程序的数据流。特别是移动B以后将会使j对i的数据依赖变为j对B的赋值指令的数据依赖。可以采用一些巧妙的方法使B的移动得以正确进行:例如,可以生成一个B得副本放在X中。但是通常情况下并不使用上述方案,因为它不仅实现困难,而且也会降低程序的运行效率。首先考虑把对B的赋值移至BNEZ指令之前的情况。令if语句前最后一个对B赋值的指令为i。如果B在被赋值前被引用,令引用指令为j。如果存在指令j,则移动B将会改变程序的数据流。特别是移动B以后将会使j对i的数据依赖变为j对B的赋值指令的数据依赖。可以采用一些巧妙的方法使B的移动得以正确进行:例如,可以生成一个B得副本放在X中。但是通常情况下并不使用上述方案,因为它不仅实现困难,而且也会降低程序的运行效率。
把对C的赋值语句移至第一分支前需要做两个步骤。第一,赋值操作被移至then部分中,这使得指令C控制相关于分支操作,即一旦else部分被选择执行,就不会再执行该操作。因此,为保证指令能够正确运行,还需把相应的对C赋值的另一个副本放至else部分中。第二,如果C跳过整个分支部分移至then部分之前也不影响分支中的数据流,则移至then部分之前。若C可以被移至if语句之前,else分支中的副本则成为多余部分,可以去掉。把对C的赋值语句移至第一分支前需要做两个步骤。第一,赋值操作被移至then部分中,这使得指令C控制相关于分支操作,即一旦else部分被选择执行,就不会再执行该操作。因此,为保证指令能够正确运行,还需把相应的对C赋值的另一个副本放至else部分中。第二,如果C跳过整个分支部分移至then部分之前也不影响分支中的数据流,则移至then部分之前。若C可以被移至if语句之前,else分支中的副本则成为多余部分,可以去掉。 全局指令调度受到很多因素的限制,是一项非常复杂的工作。各种因素相互影响,需要从中做很多权衡。因此人们设法提供硬件支持来简化它,
4.2 指令的动态调度 • 静态调度 • 依靠编译器对代码进行静态调度,以减少相关和冲突。 • 它不是在程序执行的过程中、而是在编译期间进行代码调度和优化。 • 通过把相关的指令拉开距离来减少可能产生的停顿。 • 动态调度 • 在程序的执行过程中,依靠专门硬件对代码进行调度,减少数据相关导致的停顿。
4.2.1动态调度的原理 到目前为止我们所使用流水线的最大的局限性: 指令必须顺序流出 看下面一段代码: DIVD F0 , F2 , F4 ;S1 ADDD F10 , F0 , F8 ;S2:S2对S1数据相关, S2被阻塞 SUBD F12 , F8 ,F14 ;S3:S3与S1、S2都没 有相关,但也被阻塞
为了允许乱序执行,我们将基本流水线的译码阶段为了允许乱序执行,我们将基本流水线的译码阶段 再分为两个阶段: (1)流出(Issue,IS):指令译码,检查是否存 在结构阻塞。 (2)读操作数(Read Operands,RO):当没有数 据相关引发的阻塞时就读操作数。 • 指令乱序结束带来的最大问题: 异常处理比较复杂 (精确异常处理、不精确异常处理)
4.2.2计分牌动态调度算法 例4-3:数据先读后写(WAR)相关引起的阻塞 代码序列: DIVD F0 , F2 , F4 ADDD F10 , F0 ,F8 SUBDF8, F8 , F14 指令乱序执行时就会出现先读后写相关。 • 记分牌技术的目标:在资源充足时,尽可能早地执行没有数据阻 塞的指令,达到每个时钟周期执行一条指令。
要发挥指令乱序执行的好处,必须有多条指令 同时处于执行阶段,这就要求有多个功能部件 或功能部件流水化或者两者兼有。 • 假设:处理器采用多个功能部件。 CDC 6600具有16个功能部件: 4个浮点部件, 5个存储器访问部件 7个整数操作部件 在DLX中,假设有2个乘法器、1个加法器、1 个除法部件和1个整数部件。
1.采用记分牌技术的DLX处理器的基本结构。 记分牌电路负责记录资源的使用,并负责相关检测,控制指令的流出和执行。
2.每条指令在流水线中的执行过程分为四段: (1) 流出(Issue,记为IS) 如果本指令所需的功能部件有空闲,并 且其它正在执行的指令使用的目的寄存器与 本指令的不同,记分牌就向功能部件流出本 指令,并修改记分牌内部的数据记录。 解决了指令间存在的结构相关或写后写相关。
(2) 读操作数(Read Operand,记为RO)。 记分牌需要监测源操作数寄存器中数据的 有效性,如果前面已流出的还在运行的指令不 对本指令的源操作数寄存器进行写操作,或者 一个正在工作的功能部件已经完成了对这个寄 存器的写操作,那么此操作数有效。当操作数 有效后,记分牌将启动本指令的功能部件读操 作数并开始执行。 解决了数据的先写后读(RAW)相关。 通过以上步骤,记分牌动态解决了结构相 和数据相关引发的阻塞,指令可能乱序流出。
(3) 执行(Execution,记为EX)。 (4) 写结果(Write Result,记为WR)。 记分牌知道指令执行完毕后,如果目标 寄存器空闲,就将结果写入到目标寄存器中, 然后释放本指令使用的所有资源。 • 检测先读后写(WAR)相关 • 在出现以下的情况时,就不允许指令写结果: • 前面的某条指令(按顺序流出)还没有读取操作数; • 其中某个源操作数寄存器与本指令的目的寄存器相同。
3. 记分牌需要纪录的信息分为三部分: (1) 指令状态表 记录正在执行的各条指令已经进入记 分牌DLX流水线四段中的哪一段。 (2)功能部件状态表 纪录各个功能部件的状态。每个功能部件 在状态表中都由以下九个域来纪录: Busy: 指示功能部件是否在工作 Op: 功能部件当前执行的操作 Fi: 目的寄存器编号 Fj,fk:源寄存器编号 Qj,Qk:向Rj,Rk中写结果的功能部件 Rj,Rk:表示Fj,Fk是否就绪,是否已经被使用
(3)结果寄存器状态表 每个寄存器在表中有一个域,用于纪录写入本寄存器的功能部件(编号)。如果当前正在运行的功能部件没有需要写入本寄存器的,则相应域置为空。 例4-4给出一段代码如下所示,请写出其代码运行过程中记分牌保存的信息。 LD F6 , 34(R2) LD F2 , 45(R3) MULT.D F0 , F2 , F4 SUB.D F8 , F6 , F2 DIV.D F10 , F0 , F6 ADD.D F6 , F8 , F2
DLX记分牌信息组成和记录的信息 指令状态表 IS RO EX WR 指令 LD F6 , 34(R2) √ √ √ √ LD F2 , 45(R3) √ √ √ MULTD F0 , F2 , F4 √ SUBD F8 , F6 , F2 √ DIVD F10 , F0 , F6 √ ADDD F6 , F8 , F2
结果寄存器状态表 F0 F2 F4 F6 F8 F10 … F30 部件名称 乘法1 整数加法 除法 功能部件状态表 Busy Op Fi Fj Fk Qj Qk Rj Rk 部件名称 整数 yes LD F2 R3 no 乘法1 yes MULTD F0 F2 F4 整数 no yes 乘法2 no 加法 yes SUBD F8 F6 F2整数 yes no 除法 yes DIVD F10 F0 F6 乘法1 no yes
例4-5 假设浮点流水线中执行的延迟如下: 加法需2个时钟周期 乘法需10个时钟周期 除法需40个时钟周期 代码段和记分牌信息的起始点状态跟例4-3一样。分别给出MULT.D和DIV.D准备写结果之前的记分牌状态。 解: 在分析记分牌状态之前,首先需要分析指令之间存在的相关性,因为相关性会影响指令进入记分牌DLX流水线的相应段。
(1) 第二个LD指令到MULD和SUBD、MULTD到DIVD 之间以及SUBD到ADDD之间存在着先写后读相关; (2) DIVD和ADDD之间存在着先读后写相关; (3) ADDD和SUBD指令关于浮点加法部件还存在着结 构相关。 表4-4、4-5、4-6和表4-7、4-8、4-9分别给出了MULTD指令和DIVD指令将要写结果时记分牌的状态。
表4-4 MULT.D指令记分牌的状态(指令状态表) 指令状态表 IS RO EX WR 指令 LD F6 , 34(R2) √ √ √ √ LD F2 , 45(R3) √ √ √ √ MULTD F0 , F2 , F4 √ √ √ SUBD F8 , F6 , F2 √ √ √ √ DIVD F10 , F0 , F6 √ ADDD F6 , F8 , F2 √ √ √