1、机器无关优化,授课:胡静,2018/3/8,编译技术,2,编译器的结构,出错处理,语法分析程序,语义分析程序,目标代码生成程序,词法分析程序,中间代码生成程序,代码优化程序,表格管理,2018/3/8,编译技术,3,引言,如果简单的把每个高级语言结构独立的翻译成机器代码,那么会带来相当大的运行时刻的开销。本章讨论如何消除这样的低效率因素代码改进(代码优化)在目标代码中消除不必要的指令把一个指令序列替换为一个完成相同功能的较快的指令序列上部分讲述了局部代码优化的问题,本部分主要简述全局优化问题。考虑在多个基本块之间发生的事情。,2018/3/8,编译技术,4,本章基础,大部分全局优化是基于数据流
2、分析(data-flow analyze)技术实现的。数据流分析技术是一组用以收集程序相关信息的算法。所有数据流分析的结果都具有相同的形式:对于程序中的每个指令,它们描述了该指令每次执行时必然成立的一些性质不同性质的分析方法各不相同,比如,对于常量传播分析而言,要判断在程序的每个点上,程序使用的各个变量是否在该点上具有唯一的常量值。活跃性分析确定在程序的每个点上,在某个变量中存放的值是否一定会在被读取之前被覆盖掉,如果是,我们就不需要在寄存器或内存位置上保留这个值。,2018/3/8,编译技术,5,本章结构,1、讨论一些主要的代码改进机会2、介绍数据流分析技术说明如何使用在全局内收集的信息来改
3、进代码3、介绍数据流框架的总体思想上部分中的数据流分析是这个框架的特例4、总体框架的例子,功能强大5、“部分冗余消除”的技术,用于优化程序中各个表达式求值的位置。6、讨论程序中循环的发现和分析7、在对循环进行识别的基础上,介绍一个用来解决数据流问题的算法族8、使用层次化分析来消除归纳变量(用来对循环的迭代次数进行计数的变量),2018/3/8,编译技术,6,1、优化的主要来源,优化最基本的原则:必须保持源程序的语义。编译器不可能完全理解一段程序的语义,并将其替换为一个全然不同而更加高效的等价程序。编译器只能利用一些相对低层的语义转换使用常见的性质:i+0=i,或者一些程序语义(如在同样的值上进
4、行同样的运算必然得到同样的结果),2018/3/8,编译技术,7,1.1、冗余的原因,源程序编写过程中出现的冗余重新计算某些结果更加方便和直接高级程序设计语言的副产品例如数组访问。多次引用对某一个数组的访问,可能存在很多重复的计算。高级程序设计语言屏蔽了低层具体的计算细节程序容易书写、理解和演化利用编译器来消除这些冗余,使程序获得高效高级程序设计语言屏蔽的底层细节已知。,2018/3/8,编译技术,8,1.2、本章会用到的一个例子,void quicksort(int m, int n)int i, j;int v, x;if(n v);if(i = j) break;x=ai; ai=aj;
5、 aj=x;x=ai; ai=aj; aj=x;quicksort(m,j);quicksort(i+1, n),a0保存了最小元素,amax保存了最大元素,2018/3/8,编译技术,9,1.2、本章会用到的一个例子,例子中对地址的计算首先必须要被分解为低层次的算术运算,暴露出冗余之处。假设中间表达式的结果都由临时变量来存放,并假设整数占用4个字节赋值运算x=ai的三地址语句t6=4*ix=at6每个数组访问都被翻译成一对语句,一个乘法和一个数组下标运算,2018/3/8,编译技术,10,1.2、本章会用到的一个例子,(1)i = m -1(2)j = n(3) t1 = 4*n(4)v=a
6、t1(5)i=i+1(6)t2=4*i(7)t3=at2(8)if t3v goto(9)(13)if i=j goto (23)(14)t6=4*i(15)x=at6,(16)t7=4*i(17)t8=4*j(18)t9=at8(19)at7=t9(20)t10=4*j(21)at10=x(22)goto (5)(23)t11 = 4*i(24)x=at11(25)t12=4*i(26)t13=4*n(27)t14=at13(28)at12=t14(29)t15=4*n(30)at15=x,1.2、本章会用到的一个例子,i = m -1j = nt1 = 4*nv=at1,i=i+1t2=4
7、*it3=at2if t3v goto(9),if i=j goto (23),t6=4*ix=at6t7=4*it8=4*jt9=at8at7=t9t10=4*jat10=xgoto (5),t11 = 4*ix=at11t12=4*it13=4*nt14=at13at12=t14t15=4*nat15=x,B1,B2,B3,B4,B5,B6,B2,B3,B6,B2,2018/3/8,编译技术,12,1.3、保持语义不变的转换,编译器可以使用多种方法改进程序,并保持语义不变公共子表达式消除复制传播死代码消除常量折叠如右图所示,在这个块内,对4*i和4*j进行了重复计算,虽然这些计算不是程序员
8、显式要求的。,2018/3/8,编译技术,13,1.4、全局公共子表达式,如果表达式E在某次出现之前已经被计算过了,并且E中的变量的值从那次计算之后就一直没有被改变,那么E的该次出现就称为一个公共子表达式。局部公共子表达式的消除:,不能用v代替因为在进入B6之前有可能经过B5,对a进行了赋值,2018/3/8,编译技术,15,1.5、复制传播,形如u=v这样的语句叫做复制语句,简称复制。这样的语句可以进一步的优化。,2018/3/8,编译技术,16,1.5、复制传播,在复制语句u=v之后尽可能的用v来代替u。,给了我们消除死代码的机会(对x的赋值),2018/3/8,编译技术,17,1.6、死
9、代码消除,如果一个变量在某个程序点上的值可能会在以后被使用,那么我们就说这个变量在该点上活跃。否则它在该点上就是死的。所谓死代码就是其计算结果永远不会被使用的语句。死代码多半是因为前面执行过的某些转换造成的。例如if (debug) print如果在判断之前,总有一个语句debug = FALSE;那么print语句就不可能被执行到。就可以把这个测试和print语句从目标代码中全部消除。更一般的说,如果编译时刻推导出一个表达式的值是常量,就可以用常量来代替这个表达式常量折叠。,2018/3/8,编译技术,18,1.6、死代码消除,复制传播的一个好处就是常会把一些复制语句变为死代码,2018/3
10、/8,编译技术,19,1.7、代码移动,对于优化工作而言,循环(尤其是循环内部)是一个重要的地方,因为程序常常会将它们的大部分时间花费在这上面。如果我们减少一个内部循环中的指令个数,即使因此曾加了该循环外的代码,程序的运行时间也会减少。减少循环内部代码数量的一个重要改动是代码移动。处理的是那些不管循环执行多少次都得到相同结果的表达式循环不变计算在进入循环之前就对它们求值。所谓“在循环之前”的说法假设了存在一个循环入口。所谓循环入口就是一个基本块,所有循环外部到循环的跳转指令都以它为目标。,2018/3/8,编译技术,20,1.7、代码移动,例子:while(i =limit-2) 进行代码移动
11、后得到如下等价代码:t = limit-2while(i=t)limit-2是循环不变计算,将其放到循环之前,这样不管循环执行多少次,其只计算一次。假设循环体执行n次,没有移动之前,limit-2的值要被计算n+1次。,2018/3/8,编译技术,21,1.8、归纳变量和强度消减,对于一个变量x,如果存在一个正的或负的常数c,使得每次x 被赋值时它的值总是增加c,那么x就称为“归纳变量”。把一个高代价的运算(比如乘法)替换为一个代价较低的运算(比如加法)的转换叫做强度消减。当我们沿着循环进行时,如果一组归纳变量的值的变化保持步调一致,那么我们常常可以将这组变量删剩一个。例如j和t4.由于t4=4*j,那么每次j减1,t4就减少4。处理循环时,要从内到外。,2018/3/8,编译技术,24,Thanks for your time!Questions & Answers,