第三章:流水线技术
1. 流水线的分类
可以根据不同的分类标准进行分类。
【1】部件级流水线,处理机流水线,系统级流水线
把处理机的部件进行分段,就叫做部件级流水线;处理机流水线就是我们常用的指令流水线,把指令的执行过程进行分段;系统级别流水线是把多个处理机串联起来,每个处理机作为一个段,这种又叫“宏流水线”。
【2】单功能流水线,多功能流水线
单功能流水线就是这条流水线只能完成一个功能,比如我们下面一直在说的浮点数乘法流水线。
【3】静态流水线和动态流水线
静态流水线要等当前的功能完全排出之后,才能切换到其他功能。而动态流水线则无需全部排出。
2. 流水线的性能标准
我们对一条流水线的好坏需要有一个评判的标准,我们会从三个角度去分析:吞吐率TP,加速比S,效率E。
- “吞吐率” 描述:单位时间内流水线完成的任务数量
- “加速比” 描述:流水线的耗时和顺序执行耗时的比值
- “效率” 描述:有效的占用域和总的时空区面积比值
Tips: 记住上述的描述比记住公式要靠谱,因为公式根据题目意思会变的,不固定的,请深刻理解上述描述。
下面我们将对时空图进行计算分析,默认以下字母表达:
- 段数(步骤数,纵坐标的数量)= k k k
- 任务数 = n n n
(如上图的k=4)
吞吐率的计算
TP是指单位时间内流水线完成任务的数量,自然的,分母就是总的执行时间,实际吞吐率:
T
P
=
n
(
k
+
n
−
1
)
Δ
t
TP=\frac{n}{(k+n-1)\Delta t}
TP=(k+n−1)Δtn
最大吞吐率 T P m a x = l i m n → ∞ T P TP_{max}=lim_{n\rightarrow\infty}TP TPmax=limn→∞TP,虽大吞吐率我们当然希望在单位时间内能完成的任务越多越好了。
吞吐率瓶颈处理
在动态流水线里面有可能会出现瓶颈,什么叫做瓶颈?如下图,找一下不和谐的地方:
这里明显凸出来了,如果我们按照TP的计算方法得到的如下:(假设长的
Δ
T
=
3
Δ
t
\Delta T=3\Delta t
ΔT=3Δt)
T
P
=
n
(
k
+
n
−
1
)
3
Δ
t
TP=\frac{n}{(k+n-1)3\Delta t}
TP=(k+n−1)3Δtn
被浪费的时空区(红色的地方):
如我们所见,实际上,上图有非常多的浪费,这样会变成最长的T会卡住我们的吞吐率,所以我们叫做瓶颈段,变相决定了吞吐率。这当然是不允许了,我们要想办法对时空图进行改进,我们思考一下,为什么会出现浪费?
因为
T
L
o
n
g
=
3
n
Δ
t
T_{Long}=3n\Delta t
TLong=3nΔt,需要到3次的短的T才能满足匹配。既然我们想把n统一,我们可以增加某一个段的并行处理。思想如下图:
在第四步,我们有三个可以同时处理的工人,这样一来,就能把瓶颈段消除掉了。修改之后的时空图如下:
看上面这个时空图,是不是顺眼多了,此时我们再计算TP
T
P
=
n
(
k
+
n
−
1
)
Δ
t
TP=\frac{n}{(k+n-1)\Delta t}
TP=(k+n−1)Δtn
可见,瓶颈段被消除了。
实际上有两种消除瓶颈的方法,并行处理是其中一种,我们还可以把大的步骤拆解成几个小的步骤进行消除瓶颈,详细看下面的例题。
加速比的计算
加速比的计算是最简单的,就是当前耗时和顺序执行耗时的比值:
S
=
(
k
+
n
−
1
)
Δ
t
k
n
Δ
t
S=\frac{(k+n-1)\Delta t}{kn\Delta t}
S=knΔt(k+n−1)Δt
效率的计算
效率实际上就是时空区的有效率,在图上哪些地方被浪费了?就是流水线的排入和排出的时间被浪费了。
计算效率就是计算面积比:不是红色的地方和总的面积的比值。
E
=
n
k
Δ
s
k
(
k
+
n
−
1
)
Δ
s
E=\frac{nk\Delta s}{k(k+n-1)\Delta s}
E=k(k+n−1)ΔsnkΔs
静态流水线分析例题
假如我们需要在流水线部件(如下所示)计算
∏
i
=
1
4
(
A
i
+
B
i
)
\prod_{i=1}^4 (A_i+B_i)
∏i=14(Ai+Bi)
问计算吞吐率,加速比,效率。(要求使用静态流水线,静态流水线就是并行了某一种运算之后需要完全排空才能做其他运算)
解:
首先,我们需要分解这个运算步骤,这是累积计算,但在累积之前,我们要先求和。
(
A
1
+
B
1
)
=
P
1
,
(
A
2
+
B
2
)
=
P
2
,
(
A
3
+
B
3
)
=
P
3
,
(
A
4
+
B
4
)
=
P
4
,
P
1
∗
P
2
=
V
1
,
P
3
∗
P
4
=
V
2
,
r
e
s
u
l
t
=
V
1
∗
V
2
(A_1+B_1)=P_1,\\(A_2+B_2)=P_2,\\(A_3+B_3)=P_3,\\(A_4+B_4)=P_4,\\P_1*P_2=V_1,\\P_3*P_4=V_2,\\result=V_1*V_2
(A1+B1)=P1,(A2+B2)=P2,(A3+B3)=P3,(A4+B4)=P4,P1∗P2=V1,P3∗P4=V2,result=V1∗V2
我们可以把能够并列计算的步骤列出来,首先,4个加法的运算是可以并行处理的,然后2个乘法的发生可以并列出现,最后剩下1个乘法运算。
其实我们只需要记住这样一件事:数字是任务标签,纵坐标是步骤,就不会出错了。
如上图所示,4个并行的加法运算,2个并行的乘法运算,1个乘法运算,由于是静态流水线,所以必须排空所有才能进入新的运算环节。
接下来计算吞吐率TP,加速比S,效率E。
T
P
=
7
18
Δ
t
TP=\frac{7}{18\Delta t}
TP=18Δt7
S
=
36
Δ
t
18
Δ
t
=
2
S=\frac{36\Delta t}{18\Delta t}=2
S=18Δt36Δt=2
E
=
0.25
E=0.25
E=0.25
动态流水线的分析例题
有一条动态流水线由以下五段组成:加法用1,3,4,5段,乘法用1,2,5段,第四段的时间为2
Δ
t
\Delta t
Δt,其余各段时间均为
Δ
t
\Delta t
Δt,而且流水线的输出可以直接返回输入端或者暂存在相应的流水线寄存器,若在该流水线上计算
∑
i
=
1
4
(
A
i
∗
B
i
)
\sum^4_{i=1}(A_i*B_i)
∑i=14(Ai∗Bi),求吞吐率,加速比,效率?
解:
老规矩,我们先对计算步骤来一个大解剖。
A
1
∗
B
1
=
P
1
A
2
∗
B
2
=
P
2
A
3
∗
B
3
=
P
3
A
4
∗
B
4
=
P
4
P
1
+
P
2
=
V
1
P
3
+
P
4
=
V
2
r
e
s
u
l
t
=
V
1
+
V
2
A_1*B_1=P_1\\A_2*B_2=P_2\\A_3*B_3=P_3\\A_4*B_4=P_4\\P_1+P_2=V_1\\P_3+P_4=V_2\\result=V_1+V_2
A1∗B1=P1A2∗B2=P2A3∗B3=P3A4∗B4=P4P1+P2=V1P3+P4=V2result=V1+V2
有4个可并列运算的乘法,2个可并列运算的加法,以及最后1个加法运算。
和上题不同的是这是一条动态流水线。
也就说,可以出现之前的运算没有排空的情况下,就能进行其他运算,只要对应的步骤段硬件空闲下来就行。
在4个乘法运算在进行的时候,我们只需要计算出P1和P2就可以开启加法任务,也就说下图的#1任务的开始。
T
P
=
7
16
Δ
t
TP=\frac{7}{16\Delta t}
TP=16Δt7
S
=
27
Δ
t
16
Δ
t
≈
1.69
S=\frac{27\Delta t}{16\Delta t}\approx1.69
S=16Δt27Δt≈1.69
E
=
4
∗
3
+
3
∗
5
5
∗
16
≈
0.338
E=\frac{4*3+3*5}{5*16}\approx0.338
E=5∗164∗3+3∗5≈0.338
瓶颈消除方法例题
有一指令流水线如下所示
(1) 求连续输入10条指令,该流水线的实际吞吐率和效率;
(2) 该流水线的“瓶颈”在哪一段?请采取两种不同的措施消除此“瓶颈”。对于你所给出的 两种新的流水线,连续输入10条指令时,其实际吞吐率和效率各是多少?
解:
(1)线性流水线解题,先画出时空图
T
P
=
10
200
∗
10
+
50
∗
4
=
1
220
TP=\frac{10}{200*10+50*4}=\frac{1}{220}
TP=200∗10+50∗410=2201
E = 200 ∗ 10 + 100 ∗ 10 + 50 ∗ 20 2200 ∗ 4 = 4000 8800 = 5 11 E=\frac{200*10+100*10+50*20}{2200*4}=\frac{4000}{8800}=\frac{5}{11} E=2200∗4200∗10+100∗10+50∗20=88004000=115
(2)瓶颈在S3和S4,
方法一:
把S3和S4分开拆为几步。
方法二:
由时空图可知,两者的实际吞吐率和效率一致的。
T P = 10 17 ∗ 50 = 1 85 TP=\frac{10}{17*50}=\frac{1}{85} TP=17∗5010=851
E = 1 17 E=\frac{1}{17} E=171
3. 非线性流水线的调度
详细看我的(一文讲懂流水线调度那篇文章),此处只说习题。
例题
在一个5 段流水线处理机上,各段执行时间均为Δt,需经9Δt 才能完成一个任务,其预约表如表3.8所示:
(1) 画出流水线任务调度的状态转移图。
(2) 求流水线的最优调度策略和最大吞吐率。
(3) 按最优调度策略连续输入6 个任务,求流水线的实际吞吐率是多少。
解:
(1)我们解题的逻辑是:
预约表
→
\rightarrow
→禁止表
→
\rightarrow
→初始冲突标量
→
\rightarrow
→所有冲突变量
→
\rightarrow
→状态机
S
1
S_1
S1: 8
S
2
S_2
S2: 1
S
3
S_3
S3: 3,4
S
4
S_4
S4: 1
先写出禁止表: F = { 8 , 4 , 3 , 1 } F=\{8,4,3,1\} F={8,4,3,1}
然后二进制位元化得到初始冲突变量:
C
0
=
10001101
C_0=10001101
C0=10001101
根据
n
e
w
C
0
=
s
h
r
j
∨
C
0
newC_0=shr^j\lor C_0
newC0=shrj∨C0 计算出所有的冲突变量
第一轮:
F
1
=
{
7
,
6
,
5
,
2
}
F_1=\{7,6,5,2\}
F1={7,6,5,2}
J
=
2
→
C
1
A
=
10101111
J=2 \rightarrow C_{1A}=10101111
J=2→C1A=10101111
J
=
5
→
C
1
=
10001101
=
C
0
J=5 \rightarrow C_1=10001101=C_0
J=5→C1=10001101=C0
J
=
6
→
C
1
B
=
10001111
J=6 \rightarrow C_{1B}=10001111
J=6→C1B=10001111
J
=
7
→
C
1
=
10001101
=
C
0
J=7 \rightarrow C_1=10001101=C_0
J=7→C1=10001101=C0
第二轮:
(1)
C
1
A
=
10101111
C_{1A}=10101111
C1A=10101111
F
2
=
{
7
,
5
}
F_2=\{7,5\}
F2={7,5}
J
=
7
→
C
2
=
10001101
=
C
0
J=7 \rightarrow C_{2}=10001101=C_{0}
J=7→C2=10001101=C0
J
=
5
→
C
2
=
10001101
=
C
0
J=5 \rightarrow C_{2}=10001101=C_{0}
J=5→C2=10001101=C0
(2) C 1 B = 10001111 C_{1B}=10001111 C1B=10001111
F
2
=
{
7
,
6
,
5
}
F_2=\{7,6,5\}
F2={7,6,5}
J
=
7
→
C
2
=
10001101
=
C
0
J=7 \rightarrow C_{2}=10001101=C_{0}
J=7→C2=10001101=C0
J
=
6
→
C
2
=
10001111
=
C
1
B
J=6 \rightarrow C_{2}=10001111=C_{1B}
J=6→C2=10001111=C1B
J
=
5
→
C
2
=
10001101
=
C
0
J=5 \rightarrow C_{2}=10001101=C_{0}
J=5→C2=10001101=C0
画出状态机:
(2)先找出所有的调度策略:(先写一个的,再写两个的,再写三个的,有条理的写)
枚举出所有的量:2,5,6,7
(5) AVG=5
(7) AVG=7
(2,5) AVG=3.5
(2,7) AVG=4.5
(6,5) AVG=5.5
(6,7) AVG=6.5
(6,6,7) AVG=9.5
(6,6,5) AVG=8.5
最小是(2,5),所以最优策略是(2,5)
因为最小的延迟是3.5
最大吞吐率
T
P
m
a
x
=
1
3.5
Δ
t
TP_{max}=\frac{1}{3.5 \Delta t}
TPmax=3.5Δt1
(3)
按最优调度策略画出时空图,才能计算各种性能标准:
总共需要到25个
Δ
t
\Delta t
Δt,
T
P
=
6
25
Δ
t
TP=\frac{6}{25\Delta t}
TP=25Δt6(实际吞吐率就是算完成每个任务所消耗的单位时间)
4. 流水线相关和冲突的分类
指令执行分为五个步骤,IF,ID,EX,MEM,WB,(取址,译码,执行,访存,写回)
相关
指的是两条指令之间存在依赖关系,分为:数据相关,名相关,控制相关。
- 数据相关:后面的指令需要用到前面指令的计算结果
- 名相关:两条指令用到了同一个寄存器,但是两条指令之间没有数据流动。细分为:反相关和输出相关。反相关的意思是:前面的指令读了,后面的指令写了,对于同一个寄存器;输出相关的意思是:前面的指令写了,后面的指令也写了,对于同一个寄存器。
- 控制相关:分支指令引发的,与分支情况相关不能把某些指令变更位置的。
冲突
- 结构冲突:常说的独占某个硬件部件问题,比如对同一个寄存器,一个指令准备读,一个准备写。
- 数据冲突:数据相关和名相关所导致的问题。
- 控制冲突:遇到分支指令或者其他改变PC寄存器的指令所引发的冲突。
对于数据冲突分为以下几类:
- RAW(读之前就写了)
- WAW(写之前就写了)
- WAR(写之前就读了)
5. 解决流水线的冲突
解决结构冲突
这是由于硬件资源不足所引起的,
方法【1】:“气泡”,或者叫 “停顿” (STALL)
可以去除掉指令i+3和Load指令对于Mem的使用冲突。但由于结构冲突是常见频繁的,不能这样经常停顿会大幅度影响性能。
方法【2】:采用多个独立的存储器,专门针对指令的存储器和数据的存储器,还有采用多个分离的Cache,数据Cache和指令Cache
解决数据冲突
方法【1】:使用定向技术(旁路)来解决,就是在计算结果还没完全出来之前就通过硬件把结果传到下面需要的部件。没完全出来的意思是,整条指令完整的执行了(五个步骤),但实际上,我们在EX阶段之后,在ALU的输出就能拿到结果了,这个时候就通过一条特定的数据通路把数据传到下面需要的部件。例子如下::
使用旁路进行解决:
方法【2】使用编译器优化的方法,我们利用编译器重新组织指令顺序来消除冲突,我们叫这种技术做指令调度和流水线调度。思想在之前讲流水线调度的地方。
解决控制冲突
最简单的办法是遇到分支指令就暂停下面的任务,等所有的指令排空之后再继续。但这是一种极度浪费时间的做法。
我们可以考虑使用 “延迟槽” 使流水线继续运行下去,延迟槽的指令是空的,不作为的。凑够流水线的形状。
第五章:指令级并行及其开发——硬件方法
1. 动态调度的基本思想
静态调度和动态调度的区别在于,静态调度依赖的是编译器对代码的优化,通过把有“相关”的代码拉开他们之间的距离以减少“停顿”(STALL)。我们之前讲的流水线调度问题就是属于静态调度。
而动态调度是在程序运行的过程中,依靠硬件对代码进行优化,优点如下:
- 能处理一些编译器不能很明了的事情,比如对存储器的访问
- 静态调度针对的是某一条流水线,而动态调度是可以针对许多条流水线的
动态调度的代价是“增加了硬件成本”。
我们之前在第三章讨论的流水线调度时候前提是指令都是 “按序流出” 的,这样的话,某一条指令有 “STALL”,后面的也要跟着遭殃。
所以 动态调度的能做到的就是可以让指令不按序流出,但又不影响最后的结果 。
指令流出的前提条件是 “没有数据冲突,没有结构冲突”。
我们把指令译码分为两个部分:
(1)流出:检查是否存在结构冲突
(2)读操作数:等待结构冲突消失,然后读操作数
动态调度到底是个什么样子的概念呢?假如现在有三条指令,我们对于指令的读操作数的顺序可以是动态调整的,比如说我们知道第一条指令和第二条指令存在RAW的相关,那我们在第一条指令完成MEM之前,第二条指令是不可以读操作数的(ID段),但第三条和第一条没有相关,那就可以先动态执行第三条指令的译码段(ID段),这就就是动态调度。
动态调度算法分为:记分牌算法和Tomasulo算法,Tomasulo算法是记分牌算法的改进版本。
2. Tomasulo 算法
核心思想:寄存器换名能供解决冲突。
先看一个例子:
已知有下面的指令:
DIV F0,F2,F4
ADD F6,F0,F8
STO F6,0(R1)
SUB F8,F10,F14
MUL F6,F10,F8
假如直接按照流水线顺序执行下去就会产生冲突,我们先看下产生什么冲突:
(1)这里有WAW冲突(输出相关)
DIV F0,F2,F4
ADD F6
,F0,F8
STO F6
,0(R1)
SUB F8,F10,F14
MUL F6
,F10,F8
(2)这里有个WAR冲突(写之前就读的反相关)
DIV F0,F2,F4
ADD F6,F0,F8
STO F6,0(R1)
SUB F8
,F10,F14
MUL F6,F10,F8
假如我们换名:
DIV F0,F2,F4
ADD S,F0,F8
STO S,0(R1)
SUB T,F10,F14
MUL F6,F10,T
把上面的两个F6换成S,把下面的两个F8换成T,这样就不会产生冲突了。由此知道了这样事实就是:通过寄存器换名可以解决冲突。
为了能在硬件做出我们所需要 “换成” 的新的寄存器,我们另外设置一个部件,叫做:保留站。
数据冲突的原因在于,前面的计算结果会对后面指令的计算结果产生影响,我们之前通过 “旁路” 或者叫做定向技术的事实能知道,实际上在ALU运算之后我们已经是可以得到中途的结果了,除了我们的运算器之外,比如像Load和store此类的指令,他们在整条指令未完全结束之前就拿到了数据,我们可以利用这样的时间空隙去为我们因为逻辑结果产生的等待而买单。
保留站到底怎样用呢?
比如说现在,我们ADD指令已经计算出结果了,我们可以把这个结果缓存到保留站里面,并给予他一个标识叫做ADD1,假如后面有个指令MUL需要用到刚才那条ADD计算的结果,他就直接从保留站里面读取刚才那个地址是ADD1寄存单元就行了。
我们在每个运算器的入口都设置了保留站,分为乘除的保留站和加减的保留站,那对于LOAD和STORE而言,我们设置 LOAD和STORE缓存器 来实现类似保留站的功能。
我们通过下面一个例子来说明所有的问题:
题目:
已知有下面的指令序列:
LOAD F6,34(R2)
LOAD F2,45(R3)
MUL F0,F2,F4
SUB F8,F2,F6
DIV F10,F0,F6
ADD F6,F8,F2
使用Tomasulo算法时当第一条指令完成写入结果的各种信息如下:
已知延迟周期如下:
Load 1个时钟周期
加法 2个时钟周期
乘法 10个时钟周期
除法 40个时钟周期
请写出 MUL 指令准备写结果时的各个状态表的内容。
解:
首先我们先看符号的含义,j标识第一个操作数,k标识第二个操作数,而又V和Q的含义是分别表示操作数的来源,操作数可以是直接来源于访存和访问真实的寄存器,也可以是是来自于保留站的信息。
V则是来源于真实的寄存器和访存,Q是来源于保留站,A则是Load和Store的缓存器(因为Load和Store只有一个操作数)。
我们看回上图,思考一下为什么这么填写。
此时,所有指令已经流出了,但Load2的指令还没执行完,也就是说F2的内容存在了A的缓存器里面,所以L2的A填的是45+Reg[R3],也就是说下面的指令如果需要到F2这个寄存器的内容可以直接从A里面以 “LOAD2” 的标识进行访问。
从保留站的表上,我们可以清晰的看到谁执行之前需要到谁先执行,然后一步步推进就行了。需要注意的是,执行的时间,也就是延迟的时间,当乘法在执行的时候,加法已经执行完了。
最后的结果如下:
3. 分支历史列表和分支目标缓冲器
我们解决了数据冲突和结构冲突,就要解决 “控制冲突” 了。我们使用动态的分支预测技术来对接下来发生的事件进行预测,既然要预测,那就要根据历史发生的事情再进行预测,在硬件上能实现的预测是非常局限的。
我们进行预测了分支,先提前执行了,假如错了,则回滚恢复现场。
一般有两种方法:分支历史表和分支目标缓冲器
分支历史表BHT
分支目标缓冲器BTB
BHT是在ID段(译码阶段)对BHT进行访问的。而BTB是在IF进行访问。执行的原理在于,我们查阅当前的分支指令是否在历史出现过,历史的结果是怎样的,我们就按历史的结果执行。
4. 硬件的前瞻执行
对于多流出的处理机而言,分支指令的数量是很多的,控制相关回成为ILP开发的主要障碍。为此,我们使用一种叫做前瞻执行 的技术进行解决。
上面的单纯的动态分支预测技术只是预测一个分支指令的结果,我们的前瞻执行,会预测之后再预测,我们把执行的结果写入到一个叫 再定序缓冲器 里面,我们得到确认 “正确” 之后,再把结果覆盖到寄存器和存储器里面。
前瞻执行的技术融合了以下几种思想:
- 动态分支预测
- 在控制结果没出来之前,前瞻执行后续指令
- 用动态调度对基本块进行跨基本块的调度
5. 多指令流出技术
我们如果想让CPI小于1,就需要用到多指令流出技术,我们将使用到多流出处理机,多流出处理机分为两种基本风格:超标量和超长指令字(VLIW)。
超标量的意思是每个时钟周期流出的指令数量不固定的,虽然也有上限。超长指令字的流出指令数量是固定的。
超标量计算机可以通过编译器进行静态调度,也可以使用Tomasulo算法进行硬件的动态调度,静态调度一般是顺序执行的,动态调度是乱序执行的。而VLIW是只有编译器静态调度完成的。
超标量处理机的优点:
(1)对于程序员透明,能自己检测下一条指令能否流出
(2)能对没有进行静态调度的代码进行动态优化。
考点,画出各种时空图。
例题:
设指令流水线由取指令、分析指令和执行指令 3 个部件构成,每个部件经过的时间为
Δ
\Delta
Δt,连续流入 12 条指令。
分别画出标量流水处理机以及 ILP 均为 4 的超标量处理机、超长指令字处理机、超流水处理机的时空图,并分别计算它们相对于标量流水处理机的加速比
解:
标量流水线:
多指令流出流水线的时空图:
ILP=4
超标量处理机:
超长指令处理机:
超长指令字流水线,把四条指令合并成一条长指令,所以只有三条长指令。
超流水线处理机:
把每个块分为四分。
问加速比,就是求:改进前时间/改进后的时间
改进前的时间(标量流水线):14 Δ \Delta Δt
超标量处理机的时间:5 Δ \Delta Δ t
超长指令字处理机的时间:5 Δ \Delta Δ t
超流水线处理机的时间:5.75 Δ \Delta Δ t
加速比分别是:2.8和2.435
第六章 指令级并行及其开发——软件方法
指令调度:是找出不相关的指令序列,让透明在流水线上重叠并行执行。
循环展开:把循环体的代码复制多次并按顺序排放, 然后相应调整循环的结束条件。是开发循环级并行的有效方法
软流水:是从循环不同的叠代中抽取一部分指令(循环控制指令除外)拼成一个新的循环叠代。
循环展开和软流水的对比:
循环展开主要减少由分支指令和修改循环索引变量的指令所引起的循环控制开销。
软流水使叠代内的指令级并行达到最大。
循环携带相关:是指一个循环的某个叠代中的指令与其他叠代中的指令之间的数据相关
修改方法:将存在循环携带相关的各条指令放在同一个叠代中
例子:
源代码:
for(i=1;i<=100;i=i+1){
A[i+1] = A[i] + C[i]; /* S1 */
B[i+1] = B[i] + A[i+1]; /* S2 */
}
S1和S2之间存在两种不同类型的数据相关:
循环携带RAW数据相关:相邻连词叠代的语句S1之间,相邻两次叠代中的语句S2之间。
RAW数据相关:同一叠代内的语句S2与S1之间。
修改后的:
A[1] = A[1] + B[1];
for(i=1;i<=99;i=i+1){
B[i+1] = C[i] + D[i]; /* 原S2 */
A[i+1] = A[i+1] + B[i+1]; /* 原S1 */
}
B[101] = C[100] + D[100];
EPIC
指令级并行主要由编译器负责开发,处理器为保证代码正确执行提供必要的硬件支持,只有在这些硬件机制的辅助 下这些优化技术才能高效完成。系统结构必须提供某种通信机制,使得流水线硬件能够了解编译器“安排”好的指令执行顺序。
EPIC编译器的高级优化技术
- 非绑定分支
- 谓词执行
- 前瞻执行
非绑定分支:将分支指令划分为多条粒度更小的指令,独立执行。
谓词执行:给指令集中的每条指令都增加一个执行条件,这个执行条件就叫做谓词(predicate)。
若谓词为真,指令正常执行,否则什么也不做。
前瞻执行:
谓词执行与全局指令调度的不足之处:
- 仅有少量结构全面实现谓词执行
- 全局指令调度往往需要补偿代码
EPIC通过前瞻执行提高猜测执行的效果
什么是前瞻执行?
在数据相关或控制相关尚未消除时,将指令调度到相关指令前猜测执行。通过硬件机制完成异常处理,确保正确性。(猜测前进)
全局指令调度:在保持原有数据相关和控制相关不变的前提下,尽可能地缩短包含分支结构的代码段的总执行时间。
单流出处理器——减少指令数
多流出处理器——缩短关键路径长度
基本思想:在循环体内的多个基本块间移动指令,扩大那些执行频率较高的基本块的体积
踪迹调度:踪迹调度的步骤分为两步:踪迹选择和踪迹压缩
超块调度:在踪迹调度中,如果trace入口或出口位于trace内部,编译器生成补偿代码的难度将大大增加,而且编译器很难评估这些补偿代码究竟会带来多少性能损失。超块构造——尾复制技术。
超块调度的性能特点
尾复制技术简化了补偿代码的生成过程,并降低了指令调度的复杂度。超块结构目标代码的体积也大大增加。补偿代码的生成使得编译过程更加复杂,而且由于无法准确评估由补偿代码引起的时间开销,这限制方法超块调度的应用范围。