代码优化
基本块
基本块是满足下列条件的最大的连续三地址指令序列
- 控制流只能从基本块的第一个指令进入该块。也就是说,没有跳转到基本块中间或末尾指令的转移指令
- 除了基本块的最后一个指令,控制流在离开基本块之前不会跳转或者停机
基本块划分算法
输入:三地址指令序列
输出:输入序列对应的基本块列表,其中每个指令恰好被分配给一个基本块
方法:首先,确定指令序列中哪些指令是首指令(eaders),即某个基本块的第
一个指令
- 指令序列的第一个三地址指令是一个首指令
- 任意一个条件或无条件转移指令的目标指令是一个首指令
- 紧跟在一个条件或无条件转移指令之后的指令是一个首指令
然后,每个首指令对应的基本块包括了从它自己开始,直到下一个首指令(不含)或者指令序列结尾之间的所有指令
基本块的优化
在阅读完常用的代码优化方法后阅读此节
很多重要的局部优化技术首先把一个基本块转换成为一个无环有向图(DAG)
- 基本块中的每个语句S都对应一个内部结点N
- 结点N的标号是s中的运算符;同时还有一个定值变量表被关联到N,表示S是在此基本块内最晚对表中变量进行定值的语句
- N的子结点是基本块中在s之前、最后一个对s所使用的运算分量进行定值的语句对应的结点。如果s的某个运算分量在基本块内没有在s之前被定值,则这个运算分量对应的子结点就是代表该运算分量初始值的叶结点(为区别起见,叶节点的定值变量表中的变量加上下脚标0)
- 在为语句=y+z构造结点N的时候,如果x已经在某结点M的定值变量表中,则从M的定值变量表中删除变量X
对于形如x=y+z的三地址指令如果已经有一个结点表示y+z就不往DAG中增加新的结点,而是给已经存在的结点附加定值变量x
基于基本块的DAG删除无用代码
从一个DAG上删除所有没有附加活跃变量(活跃变量是指其值可能会在以后被使用的变量)的根结,点(即没有父结点的结点)。重复应用这样的处理过程就可以从DAG中消除所有对应于无用代码的结点
数组元素赋值指令的表示
对于形如a[j]=y的三地址指令创建一个运算符为“[]=”的结点,这个结点有3个子结点,分别表示a、j和y
该结点没有定值变量表
该结点的创建将杀死所有已经建立的、其值依赖于a的结点
一个被杀死的结点不能再获得任何定值变量,也就是说,它不可能成为一个公共子表达式
DAG获得的信息
- 确定哪些变量的值在该基本块中赋值前被引用过
- 确定哪些语句计算的值可以在基本块外被引用
从DAG到基本块的重组
- 对每个具有若干定值变量的节点,构造一个三地址语句来计算其中某个变量的值
- 如果结点有多个附加的活跃变量,就必须引入复制语句,以便给每一个变量都赋予正确的值
流图
流图的结点是一些基本块
从基本块B到基本块C之间有一条边当且仅当基本块C的第一个指令可能紧跟在B的最后一条指令之后执行
此时称B是C的前驱,C是B的后继
有两种方式可以确认这样的边
- 有一个从B的结尾跳转到C的开头的条件或无条件跳转语句
- 按照原来的三地址语句序列中的顺序,C紧跟在之B后,且B的结尾不存在无条件跳转语句
常用的代码优化方法
优化的分类
- 机器无关优化:针对中间代码
- 机器相关优化:针对目标代码
- 局部代码优化:单个基本块范围内的优化
- 全局代码优化:面向多个基本块的优化
常用的优化方法
- 删除公共子表达式
- 删除无用代码
- 常量合并
- 代码移动
- 强度削弱
- 删除归纳变量
删除公共子表达式
公共子表达式定义
如果表达式x op y先前已被计算过,并且从先前的计算到现在,x op y中变量的值没有改变,那么x op y的这次出现就称为公共子表达式
删除无用代码
常用的公共子表达式消除算法和其它一些优化算法会引入一些复制语句(形如x=y的赋值语句)
复制传播:在复制语句x=y之后尽可能地用y代替x,复制传播给删除无用代码带来机会
无用代码:其计算结果永远不会被使用的语句
常量合并
如果在编译时刻推导出一个表达式的值是常量,就可以使用该常量来替代这个表达式。该技术被称为常量合并
代码移动
这个转换处理的是那些不管循环执行多少次都得到相同结果的表达式(即循环不变计算)在进入循环之前就对它们求值
循环不变的相对性
对于多重嵌套的循环,循环不变计算是相对于某个循环而言的。可能对于更加外层的循环,它就不是循环不变计算
强度削弱
用较快的操作代替较慢的操作,如用加代替乘
删除归纳变量
对于一个变量x,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c,那么x就称为归纳变量
归纳变量可以通过在每次循环迭代中进行一次简单的增量运算(加法或减法)来计算
在沿着循环运行时,如果有组归纳变量的值的变化保持步调一致,常常可以将这组变量删除为只剩一个
数据流分析
定义:组用来获取程序执行路径上的数据流信息的技术
在每一种数据流分析应用中,都会把每个程序,点和一个数据流值关联起来
IN[s]:语句S之前的数据流值
OUT[s]:语句S之后的数据流值
Fs:语句s的传递函数
设基本块B由语句s1,s2,…,Sn顺序组成
则IN[si+1]=OUT[si] i=1,2,…,n-1
到达定值分析
定值:变量x的定值是(可能)将一个值赋给x的语句
到达定值:直观地讲,如果某个变量x的一个定值d到达点p,在点p处使用的x的值可能就是由d最后赋予的
主要用途
循环不变计算的检测
如果循环中含有赋值x=y+z,而y和z所有可能的定值都在循环外面(包括y或z是常数的特殊情况),那么y+z就是循环不变计算
常量合并
如果对变量x的某次使用只有一个定值可以到达,并且该定值把一个常量赋给x,那么可以简单地把x替换为该常量
判定变量x在P点上是否未经定值就被引用
生成与杀死定值
定值d:u=v+w
该语句“生成”了一个对变量u的定值d,并“杀死”了程序中其它对u的定值
到达定值的传递函数
fd:定值d:u=v+w的传递函数
f
d
(
x
)
=
g
e
n
d
∪
(
x
−
k
i
l
l
d
)
f_d(x)=gen_d\cup(x-kill_d)
fd(x)=gend∪(x−killd)
- g e n d gen_d gend:由语句d生成的定值的集合
- k i l l d kill_d killd:由语句d杀死的定值的集合(程序中所有其它对u的定值)
到达定值的数据流方程
O
U
T
[
E
N
T
R
Y
]
=
Φ
OUT[ENTRY]=\Phi
OUT[ENTRY]=Φ
O
U
T
[
B
]
=
f
B
(
I
N
[
B
]
)
B
≠
E
N
T
R
Y
OUT[B]=f_B(IN[B])\quad B\neq ENTRY
OUT[B]=fB(IN[B])B=ENTRY
f
B
(
x
)
=
g
e
n
B
∪
(
x
−
k
i
l
l
B
)
f_B(x)=gen_B\cup(x-kill_B)
fB(x)=genB∪(x−killB)
I
N
[
B
]
=
∪
P
是
B
的
一
个
前
驱
O
U
T
[
P
]
B
≠
E
N
T
R
Y
IN[B]=\cup_{P是B的一个前驱}OUT[P]\quad B\neq ENTRY
IN[B]=∪P是B的一个前驱OUT[P]B=ENTRY
计算方法
输入:流图G,其中每个基本块B的
g
e
n
B
gen_B
genB和
k
i
l
l
B
kill_B
killB都已计算出来
输出:IN[B]和OUT[B]
方法:
O
U
T
[
E
N
T
R
Y
]
=
Φ
OUT[ENTRY]=\Phi
OUT[ENTRY]=Φ
for(除ENTRY之外的每个基本块B)
O
U
T
[
B
]
=
Φ
OUT[B]=\Phi
OUT[B]=Φ;
while(某个OUT值发生了改变)
for(除ENTRY之外的每个基本块B)
{
I
N
[
B
]
=
∪
P
是
B
的
一
个
前
驱
O
U
T
[
P
]
IN[B]=\cup_{P是B的一个前驱}OUT[P]
IN[B]=∪P是B的一个前驱OUT[P]
O
U
T
[
B
]
=
g
e
n
B
∪
(
I
N
[
B
]
−
k
i
l
l
B
)
OUT[B]=gen_B\cup(IN[B]-kill_B)
OUT[B]=genB∪(IN[B]−killB)
}
引用定值链(ud链)
- 如果块B中变量a的引用之前有a的定值,那么只有a的最后一次定值会在该引用的ud链中
- 如果块B中变量a的引用之前没有a的定值,那么a的这次引用的ud链就是N[B]中a的定值的集合
活跃变量分析
对于变量x和程序点p,如果在流图中沿着从p开始的某条路径会引用变量x在p点的值,则称变量x在点p是活跃的,否则称变量x在点p不活跃
主要用途
- 删除无用变量
- 为基本块分配寄存器
可用表达式分析
如果从流图的首节点到达程序点P的每条路径都对表达式x op y进行计算,并且从最后一个这样的计算到点p之间没有再次对x或y定值,那么表达式x op y在点p是可用的