本文导读:
一、并行技术1.并行技术分类2.新技术的设计与实现3.指令周期二、流水线技术1.什么是流水线2.指令重叠方式3.流水工作设计4.流水线的描述方法(时空图)5.流水线特点三、流水线的分类(了解)四、流水线相关及冲突(重点)1.流水线相关2.流水线冲突3.流水线冲突带来问题4.数据冲突及其解决方案5.结构冲突及其解决方案6.控制冲突及其解决方案五、流水线性能分析(含例题讲解)1.流水线的基本参数——吞吐率2.流水线的基本参数——加速比3.流水线的基本参数——效率4.结果分析5.有关流水线性能的若干问题 六、循环展开优化1.指令调度2.循环展开 七、多指令流出技术(拓展了解)1.超标量2.超长指令字
一、 并行技术
- 现流行的并行技术大都可以从三个方面实现:
- 资源重复:如多核
- 资源共享:如CPU分时技术
- 时间重叠:如流水线技术
- 新技术的设计与实现
- 引入新技术进行优化
- 出现新问题
- 解决新问题
采用一些机制可以解决
权衡
- 整体评估、反馈、再改进
- 指令周期
- 单周期处理机模型:一个周期完成一个指令(每个周期是等长的),指令长度可能不一样,会造成很大的浪费
- 多周期处理机模型:将一个指令的完成划分成若干个周期来实现
- 流水线模型
二、流水线技术
1. 什么是流水线?
- 计算机中的流水线是把一个重复的过程分解为若干个子过程,每个子过程与其他子过程并行进行。由于这种工作方式与工厂中的生产流水线十分相似, 因此称为流水线技术
- 从本质上讲,流水线技术是一种时间并行技术。
2.指令重叠方式
- 顺序执行:控制简单,节省设备;但是速度慢,功能部件的利用率低
- 重叠执行方式 :指令的执行时间缩短 ,功能部件的利用率明显提高 ;但是需要增加一些硬件;控制过程稍复杂
3.流水线工作设计
- 基本思想:延伸重叠方式,使指令解释过程进一步细化, 提高各部件的利用率,以提高指令执行速度
- 理想目标:完成任务的时间与操作处理过程无关,只与提供操作的速度有关(假设一个任务有n个指令,将完成一个指令分为m个段,每段执行时间为△t ,则理想目标是完成任务的时间是T=m△t+(n-1)△t;当n >> m时,T=(n-1)△t。 指令执行频率为 1 / △t: 即 与m无关,只和提供操作的速度△t有关)。
4.流水线的描述方法
- 时间—空间图 Ⅰ:
横坐标:表示时间,即各个任务在流水线中所经过的时间
纵坐标:表示空间,即流水线的各个子过程,也称为级、 段、流水线深度(Stage)
- 时间—空间图 Ⅱ: 横坐标:表示时间,即各个任务或指令在流水线中 所在该时刻所对应的子过程
纵坐标:表示某个任务或某条指令,即流水线依次 处理的任务或指令
IF:Instruction Fetch, 取指令,用到部件:指令存储器,Adder( 全加器,full-adder,是用门 实现两个二进制数相加并求出和的组合线路,称为一位全加器。一位全加器可以处理低位进位,并输出本位加法进位。多个一位全加器进行级联可以得到多位全加器。常用二进制四位全加器74LS283)
ID:Instruction Decode, 译码(应该是取数同时译码的过程),用到部件:指令译码器寄存器堆读口(这里面的寄存器堆的读口和写口可以看作两个不同的部件),这块有大量寄存器,WB也是从写口将数据写到这块的寄存器中。
EX:Exec, 执行,计算内存单元地址。用到部件:ALU,扩展器
MEM: 访存,从数据存储器中读。用到部件:数据存储器。
WB:Write Back, 写回,将数据写到寄存器中。用到部件:寄存器堆写口。
- 工作流程:分装入、流水、排空 三个流程
- 同步处理:功能部件 + 锁存器
- 硬件要求:
独立工作的各子功能部件;
各部件处理时间尽可能相等,争取最大工作频率;
解决访存冲突,即 允许不同指令的同时读、写功能;
解决同步问题,保证以相同的速度处理
5.流水线特点
在流水线处理器中, 连续任务是充分发挥流水线的效率必要条件之一
一个任务的执行过程可以划分成多个有联系的子任务,每个子任务由一个专门的功能部件实现
每个功能部件后面都有缓冲存储部件,用于缓冲本步骤的执行结果
同时有多个任务在执行;每个子任务的功能部件并行工作,但各个功能部件上正在执行的是不同的任务
各子任务执行的时间应尽可能相近
流水线有装入时间和排空时间,只有 流水线完全充满时, 流水线的效率能得到充分发挥
三、流水线的分类(了解)
1.按处理级别
操作级流水操作重叠
指令级流水指令执行重叠
处理器级(宏流水线)任务重叠
2.按功能分
单功能流水线:流水线只完成一种固定功能
多功能流水线:流水线可以完成多种功能,如 TI公司的ASC机,8段流水线,能够实现:定点加减 法、定点乘法、浮点加法等功能
3.按同一时间内各段之间的连接方式分
静态多功能流水线 :同一时间内,多功能结构只能按一种功能的连接方式工作。
动态多功能流水线:在同一时间内,可以有多种功能的连接方式同时工作
4.按处理的数据类型
标量流水线
向量流水线
5.按控制方式
同步流水线
异步流水线:当Si功能段要向Si+1段传送数据时,首 先发出就绪信号,Si+1功能段收到信号后,向Si回送 一个回答信号。
6.按任务从输出端的流出顺序
顺序流水方式:指令流出顺序 = 指令流入顺序
乱序流水方式:指令流出顺序 != 指令流入顺序
7. 线性流水线——不带反馈回路的流水线
非线性流水线——带反馈回路的流水线
线性流水线和非线性流水线对比
相同之处 : 都有从第一个功能段到最后一个功能段的单向传输线。
不同之处 :
非线性流水线一般有前馈线路或反馈线路;
非线性流水线的输出端经常不在最后一个功能段,而可能从中间的任意一个功能段输出。
任务经过流水线时,可能要多次经过同一功能段。
仅用功能段之间的连接图并不能清楚地描述一个非线性流水线。一般需要连接图和一张预约表共同描述。
四、流水线相关及冲突(重点)
1. 流水线相关(dependence): 两条指令之间存在某种依赖关系。
- 数据相关
先写后读:后面指令用到前面指令的结果
或间接使用
- 名相关:换名技术
使用相同的寄存器或存储器单元名称/地址
反相关: 先读后写
输出相关: 先写后写
真相关: 先写后读
- 控制相关
由分支指令引起的相关
2. 流水线冲突(hazards)
流水线冲突是指对于具体的流水线来说,由于"相关"的存在,使得指令流中的下一条指令不能在指定的时钟周期执行
- 数据冲突:当指令在流水线中重叠执行时,因需要用到前面指令的执行结果而发生的冲突
- 控制冲突:流水线遇到分支指令和其他会改变PC值的指令所引起的冲突
- 结构冲突:因硬件资源满足不了指令重叠执行的要求而发生的冲突,比如说,前面后面指令同时访问存储器
3.流水线冲突 带来问题
- 导致错误的执行结果
- 流水线可能会出现停顿,从而降低流水线的效率和实际的加速比
- 我们约定当一条指令被暂停时,在该暂停指令之后流出的所有指令都要被暂停,而在该暂停指令之前流出的指令则继续进行(否则就永远无法消除冲突)
4.数据冲突:
举例: 指令一:DADD R1,R2,R3 R2 + R3 --> R3
指令二:DSUB R4,R1,R5 R1 - R5 --> R4
指令三:XOR R6,R1,R7 R1 xor R7 -->R6
指令四:AND R8,R1,R9 R1 and R9 -->R8
指令五:OR R10,R1,R11 R1 or R11 --> R10
对上图的解读:
以指令一DADD R1,R2,R3为例:
第一周期:首先在IM中取出加法指令;
第二周期:在Reg中取出R2, R3寄存器中的值;
第三周期:在ALU中做R2 + R3加法运算;
第四周期:空;
第五周期:将R2 + R3的结果写回到R1寄存器中;
很显然,寄存器R1的正确数据是在第一条指令执行到第五周期才产生的,而后面四条指令都用到了直到第五周期才产生的R1数据,但是有些指令(指令二、三、四)不到第五周期就需要使用R1的数据(如指令二的减法指令在第二周期就需要使用R1的数据),这显然是不合理的,会 产生数据冲突。下面逐个情况讨论数据冲突及其解决办法:
- 同一个周期数据冲突的情况:以指令四AND R8,R1,R9为例:在第五周期,第一条指令R2 + R3结果正准备写入到R1中,但是此时第四条指令现在就需要使用R1(此时R1的值还不是R1 + R2),显然会出现问题
解决办法: 利用寄存器读写数据非常快的特点,无论是从存储器中读还是写入存储器往往都用不了一个完整周期(事实上半个周期就足够完成寄存器的读写操作),所以可以设计规定: 上升沿写数据,下降沿读(取)数据。这样一来,在第五周期的上升沿(即第五周期初时刻)将R2 + R3的结果写到R1寄存器中,在第五周期下降沿(第五周期末)从R1寄存器中取出数据的方法解决此类数据冲突的问题。
- 在R1写入寄存器的前一个周期(第四周期)就需要使用R1数据的情况:如指令三 XOR R6,R1,R7:这种情况显然不适合利用上述解决办法解决。但是可以换一个角度:虽然这个时候没有任何办法在Reg中取出正确的R1值,但是可以从ALU部件下手,因为从Reg取出R1数据最终目的就是计算R1 XOR R7,实际上就是计算(R2 + R3) XOR R7,所以只要保证输入到的ALU中的数据是(R2 +R3)就可以了,那如何使(R2 + R3)的结果输入到ALU参与(R2 + R3) XOR R7 运算呢?
解决办法:采用 数据定向路径技术解决!如下图所示(蓝色粗线所示):在第五周期初,对于指令三来说,从Reg读出的错误的R1的值即将进入到ALU中参与XOR运算,此时,我们从DM后拉一根线到ALU之前,因为此时刻(第五周期初,即第四周期末)(R2 + R3)的结果正处在DM后面,这样一来,(R2 + R3)的结果会通过我们刚拉的那根线传到ALU前,这时候,我们在进入ALU之前设置一个多路选通器,不选择错误的R1值,而是选择使用传过来的(R2 +R3)值,问题就解决了!
- 同样道理,对于指令二:DSUB R4,R1,R5,在第四周期初要用到(R2 + R3)的值,可以从DM前(或者ALU后)拉一根线到ALU前,如绿色线所示,同样可以解决问题。
- 综上所述,综对于数据冲突来说,可以通过定向技术减少数据冲突引起的停顿 (定向技术也称为旁路或短路)。但是并不是所有的数据冲突都可以用定向技术来解决:如下例:
LD R1,0(R2)
DADD R4,R1,R5
AND R6,R1,R7
XOR R8,R1,R9
解决上述问题:增加流水线互锁硬件,插入“暂停”(气泡)。
5.结构冲突
- 如果某种指令组合因为资源冲突而不能正常执行,则称该处理机有结构冲突(比如说,两条指令在同一周期同时访问内存,如下图所示)
- 常见的导致结构相关的原因:
功能部件不是完全流水
资源份数不够
- 有些流水线处理机只有一个存储器,将数据和指令放在一起,访存指令会导致访存冲突
解决办法Ⅰ:插入暂停周期 (“流水线气泡”或“气泡”) ,如下图所示:
解决方法Ⅱ: 设置相互独立的指令存储器(指令Cache )和数据存储器(指令Cache )。
- 有时流水线设计者允许结构冲突的存在
主要原因:减少硬件成本 : 如果把流水线中的所有功能单元完全流水化,或者重复设置足够份数,那么所花费的成本将相当高
- 控制冲突
类似于判断、循环、函数调用、递归等涉及到跳转的会涉及到控制冲突。
1 //设s , i 初值都为0;2 if (i < 10)3 { 4 /*指令一:*/ s = s + i;5 /*指令二:*/ i++;6 }7 /*指令三:*/ s = s - 2;
以此为例,在流水线环境中,首先执行指令一,紧接着执行指令二(当然不是直接执行高级语言,而是该句高级语言对应的机器语言),重点是下一步,按照我们思维,因为此时i = 1 < 10;所以应该继续执行指令一,但是可惜计算机很“傻”,它此时还真不知道该执行指令一还是指令三,这是为什么?
指令编号 | 时钟周期 | ||||||
1 | 2 | 3 | 4 | 5 | 6 | 7 | |
DADD Rs, Rs ,Ri (s = s + i ) | IF | ID | EX | MEM | WB | ||
DADD Ri, Ri, 1 ( i = i + 1) | IF | ID | EX | MEM | WB | ||
??? | Start |
结合上表所示,前两条指令按照流水线方式依次执行,关于第三条指令,按照正常逻辑:若 i >= 10,则应该执行指令三;若是i < 10,则应该执行指令一。现在重点是落在i 此时到底是多少?请注意,此时正确的i 值取决于指令二:i++,但是在指令二中,i值直到第6周期才被更新(写回到Ri寄存器),就算使用数据定向也不可能实现在第三周期初就能得到正确的i值,所以说, 涉及到分支跳转语句时,计算机是不知道是该执行分支语句还是该忽略分支语句继续向下顺序执行。好的,现在先把这个结论放在一边。
对于上述if分支语句而言,执行分支指令的结果就两种:
- 分支成功(继续执行if中的语句):此时,PC值改变为分支转移的目标地址 。 在条件判定和转移地址计算都完成后,才改变PC值。
- 分支失败(执行if函数体外面的语句):PC的值保持正常递增, 指向顺序的下一条指令。
在上述的5段流水线中,改变PC值是在MEM段进行的,会给流水线带来了3个时钟周期的延迟(停顿),前面我们说过,流水线出现停顿(暂停)会影响流水线的效率和加速比(尤其是面对上亿条指令时,停顿会经常出现)
- 可采取两种措施来减少分支延迟。
在流水线中尽早判断出分支转移是否成功;
尽早计算出分支目标地址
关于第一个措施,上面我们已经分析过了, 计算机不能正确判断出分支转移是否成功,那该怎么办呢?
第一种减少延迟时间的方法:猜!(确实是这样的!)
猜的结果无非就两种:猜对或者猜错了。
这里面我们假设分支失败:则允许分支指令后的指令继续在流水线中流动,就好象什么都没发生似的
- 如果猜对了,即已经确定分支失败:将分支指令看作是一条普通指令,流水线正常流动
- 如果猜错了,即已经确定分支成功,那也不要紧,流水线就把在分支指令之后 取出的所有指令转化为空操作(到最后需要将结果写入到寄存器时,就通过使能信号不让它写入就可以了 ),并按分支目地重新取指令执行。
但是这有一个前提: 要保证:分支结果出来之前不会改变处理机的状态,以 便一旦猜错时,处理机能够回退到原先的状态
第二种减少延迟时间的方法:延迟分支(指令调度)
- 主要思想:
从逻辑上“延长”分支指令的执行时间。把延迟分支看成是由原来的分支指令和若干个 延迟槽构成,不管分支是否成功,都要按顺序执行延迟槽中的指令
分支延迟槽中的指令“掩盖”了流水线原来必须插入的暂停周期(延迟时间),从而达到减少延迟时间的目的。
- 分支延迟指令的调度任务:在延迟槽中放入有用的指令( 由编译器完成)
- 三种调度方法: 从前调度 从目标处调度 从失败处调度
- 分支延迟受到两个方面的限制:
可以被放入延迟槽中的指令要满足一定的条件。
编译器预测分支转移方向的能力。
- 进一步改进:分支取消机制(取消分支)
当分支的实际执行方向和事先所预测的一样时, 执行分支延迟槽中的指令,否则就将分支延迟槽中的 指令转化成一个空操作
五、流水线性能分析
1.流水线的基本参数 ——吞吐率
- 吞吐率(Through Put)—— 单位时间内流水线完成任务数量或输出的结果数:TP = n / Tk
其中:n 为任务数; TK 为完成n个任务所用时间
在K级流水线中, 各段执行时间相等,输入任务连续的情况下,时钟周期为△t ,则完成n个任务需要的总时间为 Tk=(k+n-1) △t
因此吞吐率为 TP = n / (k+n-1) △t 当n-->无穷大时,TP MAX = 1 / △t
各段执行时间不等时:
2.流水线的基本参数—— 加速比
- 加速比——完成一批任务,不使用流水线时间与使用流水线所用的时间之比。
S = 顺序执行时间T0 / 流水线执行时间Tk
- 各段执行时间相等,输入任务连续的情况下:S = k*n*△t / (k + n - 1)△t = k*n / (k + n -1) 当n—>无穷大时, S = k
3.流水线的基本参数—— 效率(数格子)
效率(Efficiency)—— 流水线的设备利用率。在时空图上,流水线的效率定义为n个任务时间占用的时空区,与k个功能段总的时空之比
E = n个任务占用的时空区(有颜色的格子数) / k个流水段的总的时空区(所有格子数) = T0 / k * TK = k*n*△t / k * (k + n - 1)△t = k*n / k * (k + n -1) 当n—>无穷大时, E = 1
- 例一:流水线性能分析举例
每个浮点加法都需要经过4级:求阶差、对阶、尾数加、规格化
用一条4段浮点加法器流水线求8个浮点数的和:Z = A+B+C+D+E+G+F+H,求流水线的吞吐率、效率、加速比。
解:首先画出时空图
吞吐率: TP = n / Tk = 7 / (15 * △t ) = 0.47 / △t
加速比: S = 4 * 7 / (15)= 1.87
效 率: E = 7 * 4 / (15 * 4)= 0.47 —— 数格子
- 例2:(a1+b1)*(a2+b2)*(a3+b3)*(a4+b4)在静态、双功能(加法和乘法)流水线上实现
计算顺序: x1=a1+b1, x2=a2+b2, x3=a3+b3, x4=a4+b4; y1=x1*x2, y2=x3*x4; z=y1*y2
首先画出时空图(第一个是错误画法):
下面一个是正确画法:
吞吐率: TP = n / Tk = 7 / (17 * △t )
加速比: S = (4*3 + 3*5) / (17)= 1.88
效 率: E =(4*3 + 3*5) / (17 * 6)= 0.264 —— 数格子
4.结果分析:
- 建立、排空时间过多(流水线断流—数据相关和操作变换)
- 功能切换必须等待排空后进行。(静态通病)
- 动态流水线结果如何?(减少2个Δt)
- 如何提高流水线效率? 尽量细化各功能段,尽量减少功能切换,尽量减少数据相关,尽量增加一次处理的指令数量。
- 流水线适用范围? 流水线适合于操作相同、操作数间无相关性的多个指令的执行。
5.有关流水线性能的若干问题
- 流水线并不能减少(而且一般是增加)单条指令的执行时间,但却能提高吞吐率。
- 适当增加流水线的深度(段数)可以提高流水线的性能。
- 流水线的深度(级数)受限于流水线的延迟和流水线的额外开销。
- 相关问题。如果流水线中的指令相互独立 ,则可以充分发挥流水线的性能。但在实际中 ,指令间可能会是相互依赖,这会降低流水线的性能。
六、循环展开优化
在流水线中,往往因为指令顺序安排不合理而导致CPU等待空转,产生延迟,影响流水线效率。
解决办法: 循环展开和指令调度
前提假设:假设采用MIPS的5段整数流水线:
分支的延迟:1个时钟周期。
整数load指令的延迟:1个时钟周期。
整数运算部件是全流水或者重复设置了足够的份数。
从例题入手理解:
例: 对于下面的源代码,转换成MIPS汇编语言, 在不进行指令调度和进行指令调度两种情况下,分析其代码一次循环所需的执行时间。
for (i=1024; i>=0; i--) x[i] = x[i] + s;
Loop:L.D F0,0(R1)
ADD.D F4,F0,F2
S.D F4, 0(R1)
DADDIU R1,R1,#-8
BNE R1,R2,Loop
其中: 整数寄存器R1:指向向量中的当前元素(初值为向量中最高端元素的地址)
浮点寄存器F2:用于保存常数s
第二部浮点加法需要4个周期
分析:
假设一个浮点计算部件需要4周期完成一个计算,若该部件不使用流水线,则延迟为 :
2.循环展开:
在上例中, 只有L.D、ADD.D和S.D这3条指令是有效操作 (取、加、存) ,占用3个时钟周期。 而DADDIU、空转和BEN这3个时钟周期都是附加的循环控制开销。 可以通过循环展开的方式消除冗余,减少循环控制开销。
- 循环展开技术
把循环体的代码复制多次并按顺序排列,然后相应调整循环的结束条件
这给编译器进行指令调度带来了更大的空间
将上述例子中的循环展开3次得到4个循环体,然后对展开 后的指令序列在不调度和调度两种情况下,分析代码的性能。
分配寄存器(不重复使用寄存器 ):
F0、F4:用于展开后的第1个循环体
F2:保存常数
F6、F8:展开后的第2个循环体
F10、F12:第3个循环体
F14、F16:第4个循环体
下面分三种情况比较循环展开和指令调度节省的时间:
- 第一种情况:
- 第二种情况:
对指令序列进行优化调度,以减少空转周期
浮点加法部件采用流水指令流出时钟
- 第三种情况:
对指令序列进行优化调度,以减少空转周期
浮点运算部件不采用流水
- 循环展开和指令调度时要注意以下几个方面:
保证正确性。 在循环展开和调度过程中尤其要注意两个地方的正确性:循环控制,操作数偏移量的修改。
注意有效性。 只有能够找到不同循环体之间的无关性,才能有效 地使用循环展开。
使用不同的寄存器。 (否则可能导致新的冲突)
删除多余的测试指令和分支指令,并对循环结束代码和新的循环体代码进行相应的修正。
注意对存储器数据的相关性分析
注意新的相关性
3.指令级并行
指令调度可以通过两种形式实现:静态调度和动态调度
- 静态调度
依靠编译器(编译器需要做的很复杂,很完善)对代码进行静态调度,以减少相关和冲突。
它不是在程序执行的过程中、而是在编译期间进行代码调度和优化。
通过把相关的指令拉开距离来减少可能产生的停顿。
- 动态调度
在程序的执行过程中,依靠专门硬件对代码进行调度,减少数据相关导致的停顿。
能够处理一些在编译时情况不明的相关(比如涉及到存储器访问的相关),并简化了编译器;
能够使本来是面向某一流水线优化编译的代码在其他的流水线(动态调度)上也能高效地执行。
以硬件复杂性的显著增加为代价
七、多指令流出技术(拓展了解)
- CPI:平均每条指令执行周期数
- 前面介绍的流水线最理想(实际上达不到)的状态就是每个周期执行一条指令,即CPI = 1,速度的提升似乎就此止步了,但是人类的智慧超出你想象,我们有办法让CPI < 1 吗?(即每条指令执行时间连一个周期也用不了)
- 答案是肯定的,我们可以通过多指令流出技术实现这一点。
1.多指令流出处理机有两种实现方式:
- 超标量(Superscalar)
在每个时钟周期流出的指令条数不固定,依代码的具体情况而定。(但有上限)
设这个上限为n,就称该处理机为n流出。
可以通过编译器进行静态调度,也可以基于 Tomasulo算法进行动态调度。
动态超标量调度技术的效果更好
- 超长指令字VLIW(Very Long Instruction Word)
在每个时钟周期流出的指令条数是固定的,这些指令构成一条长指令或者一个指令包(就是把能并行执行的多条指令组装成一条很长的指令,通常是100多位到几百位 )。
指令包中,指令之间的并行性是通过指令显式地表示出来的。
设置多个功能部件。
指令字被分割成一些字段,每个字段称为一个操作槽 ,直接独立地控制一个功能部件。
在VLIW处理机中, 所有的处理和指令调度都是由编译器静态完成的。
2.指令多流出处理器受哪些因素的限制呢?
主要受以下三个方面的影响:
程序所固有的指令级并行性。
硬件实现上的困难。
超标量和超长指令字处理器固有的技术限制。
3.由于指令多流出处理机受到多方面的限制,所以又引入了 超流水线处理机
超流水线处理机的特点是将每个流水段进一步细分,这样在一个时钟周期内能够分时流出多条指令。
对于一台每个时钟周期能流出n条指令的超流水线计算机来说,这n条指令不是同时流出的,而是每隔1/n 个时钟周期流出一条指令。
实际上该超流水线计算机的流水线周期为1/n个 时钟周期。
下面是一台每个时钟周期分时流出两条指令的超流水线计算机的时空图