思考一个问题:我们可以再抽象语法树上做编译优化吗?
答案是否定的,如果在抽象语法树上做编译优化的话,程序员所写的可能包含错误的代码,可能就被删除了,比如,对下面的程序做不可达代码删除优化,printf中应该首先给一个格式化字符串,而不是一个整型数,所以存在语义错误,如果先做优化,由于总是执行then分支,else分支执行不到,所以会把else分支删除,只保留if部分,那么编译器就不会爆语义错误了。这就违反了编译器中的一个基本原则--不能违反源程序的语义性质,这样的程序做类型检查时,程序员期待编译器报错,如果优化先做的话,那么这个错误消失了,所以对程序员理解这个程序的含义是相互矛盾的。
if (1)
{
...
}
else
{
printf(2);
}
所以必须要在语义分析做完之后,再做编译优化。
(注:图中的抽象语法树是做完语义分析之后的)
优化并不是局限于某一个中间表示,而是依附于不同的中间表示,在每一层上都可以做。
什么是代码优化?
代码优化是对被优化的程序进行的一种语义保持的变换
语义保持:程序的可观察行为不能改变
变换的目的是让程序能够比变换前更小、更快、Cache行为更好、更节能等等
不存在“完全优化”
等价于停机问题:给定程序p(循环不终止的程序),把Opt(p)和下面的程序比较
(扩展:停机问题是可计算性和计算复杂性的基本定理:给定程序P,是否存在算法Q,Q将P作为输入,判断P是否可以运行终止。这是个不可计算问题,即不存在这样的算法Q)
L:
jmp L
如果存在一个完全优化算法Opt,那么最终会将程序p优化成上面的样子,但是由于停机问题是不可计算问题,所以不可能存在这样的完全优化算法Opt。
这样的结论告诉我们,编译优化是一个没有尽头的过程。换句话说,我做完了第一个优化,将程序P通过O1优化为P1,为了加快程序的运行,我们可以继续通过O2将程序P1优化为P2,可以这样一直进行下去,也就意味着程序总是可以被优化的。
代码优化很困难
● 不能保证优化总能产生“好”的结果
● 优化的顺序和组合很关键
● 很多优化问题是非确定的
● 优化的正确性论证很微妙
对待编译优化正确的观点
(1)“把该做对的做对”,不是任何程序都会同概率出现,所以能处理大部分常见情况的优化就可以接受。
(2) “不期待完美编译器”,如果一个编译器有足够多的优化,则就是一个好的编译器。
编译优化路线图
(1)前端优化
局部的、流不敏感的
常量折叠、代数优化、死代码删除等
(2)中期优化
全局的、流敏感的
常量传播、拷贝传播、死代码删除、公共子表达式删除等
(3)后端优化
在后端(汇编代码级)进行
寄存器分配、指令调度、窥孔优化等