编译原理-第十章--代码优化
- 格式:doc
- 大小:119.00 KB
- 文档页数:10
编译原理优化编译器的基本原理与方法编译原理优化编译器是计算机科学中的一个重要研究领域,旨在通过优化编译器的设计和实现,提高程序的执行效率和性能。
本文将介绍编译原理优化编译器的基本原理与方法,从静态分析、代码优化和代码生成三个方面进行论述。
一、静态分析静态分析是编译原理优化编译器的核心技术之一,其目的是对程序进行静态分析,获取程序的关键信息,为后续的优化过程提供依据。
在静态分析中常用的方法包括符号表、控制流图、数据流分析等。
1. 符号表符号表是编译过程中记录程序中标识符信息的数据结构。
通过对程序的符号表进行分析,可以获取程序中变量的类型、作用域等信息,为后续的优化过程提供依据。
2. 控制流图控制流图描述程序中的控制流转换关系,对于提高程序性能和理解程序结构都具有重要作用。
通过对程序的控制流图进行静态分析,可以识别循环、条件语句等特点,为后续的优化过程提供依据。
3. 数据流分析数据流分析是一种静态分析方法,用于分析程序中变量的值和变量之间的依赖关系。
通过对程序的数据流分析,可以识别出未使用的变量、常量传递等问题,为后续的优化过程提供依据。
二、代码优化代码优化是编译原理优化编译器的关键步骤,其目的是通过对程序代码的优化,提高程序的执行效率和性能。
在代码优化中常用的方法包括常量折叠、公共子表达式消除、循环展开等。
1. 常量折叠常量折叠是一种代码优化技术,通过将程序中的常量表达式计算出结果,减少运行时的计算量,从而提高程序的执行效率。
2. 公共子表达式消除公共子表达式是指在程序中多次出现的相同表达式。
通过识别和消除公共子表达式,可以减少重复计算,提高程序的执行效率。
3. 循环展开循环展开是一种代码优化技术,通过将循环体中的代码重复展开多次,减少循环的迭代次数,从而提高程序的执行效率。
三、代码生成代码生成是编译原理优化编译器的最后一步,其目的是将经过优化的中间代码转换为目标机器代码。
在代码生成中常用的方法包括指令选择、寄存器分配等。
第十章代码优化某些编译程序在中间代码或目标代码生成之后要对生成的代码进行优化。
所谓优化,实质上是对代码进行等价变换,使得变换后的代码运行结果与变换前代码运行结果相同,而运行速度加大或占用存储空间少,或两者都有。
优化可在编译的不同阶段进行,对同一阶段,涉及的程序范围也不同,在同一范围内,可进行多种优化。
一般,优化工作阶段可在中间代码生成之后和(或)目标代码生成之后进行。
中间代码的优化是对中间代码进行等价变换。
目标代码的优化是在目标代码生成之后进行的,因为生成的目标代码对应于具体的计算机,因此,这一类优化在很大程度上依赖于具体的机器,我们不做详细讨论。
另外依据优化所涉及的程序范围,又可分为局部优化、循环优化和全局优化三个不同的级别。
局部优化指的是在只有一个入口、一个出口的基本程序块上进行的优化。
循环优化对循环中的代码进行的优化。
全局优化是在整个程序范围内进行的优化。
本章重点:局部优化基本块的DAG表示第一节优化技术简介为了说明问题,我们来看下面这个例子,源程序是:P :=0For I :=1 to 20 doP :=P+A[I]*B[I];经过编译得到的中间代码如图10-1-1所示,这个程序段由B1和B2两个部分组成,B2是一个循环,假定机器按字节编址。
那么,对于这个中间代码段,可进行如下这些优化。
1、删除多余运算(删除公共子表达式)优化的目的在于使目标代码执行速度较快。
图10-1-1中间代码(3)和(6)中都有4*I的运算,而从(3)到(6)没有对I赋值,显然,两次计算机的值是相等的。
所以,(6)的运算是多余的。
我们可以把(6)变换成:T4 :=T1。
这种优化称为删除多余运算或称为删除公共子表达式。
2、代码外提减少循环中代码总数的一个重要办法是代码外提。
这种变换把循环不变运算,即其结果独立于循环执行次数的表达式,提到循环的前面。
使之只在循环外计算一次,上例中,我们可以把(4)和(7)提到循环外。
经过删除多余运算和代码外提后,代码变成图10-1-2。
编译原理常用的优化方法说实话编译原理常用的优化方法这事儿,我一开始也是瞎摸索。
我最早接触的时候就听说过常量折叠这方法。
就好比你去超市买东西,算总价的时候如果是几个固定数字相加,直接算出一个结果来写着,而不是每次都临时计算,这在编译里面就是常量折叠。
我之前弄个简单的算术表达式计算程序的时候就想,这能有啥难的,照着语法规则写不就完了嘛。
结果发现代码运行起来就特别慢,尤其是表达式里有好多常量运算的时候。
后来才知道有常量折叠这招。
我就开始改我的代码,把那些能在编译的时候算出来的常量表达式,直接就用结果代替。
这一优化,程序运行速度就明显快了好多。
还有公共子表达式消除这个方法。
我试过很多次了。
打个比方,你从家到学校有两条路,一条直路,一条弯路,你每次都走弯路,后来发现有直路存在,那肯定就走直路效率高。
在编译里面,如果有相同的子表达式计算多次,就只计算一次然后复用结果。
我在一个处理复杂逻辑表达式求值的程序里尝试这个优化方法。
一开始,我都不知道咋找那些相同的子表达式,费了好大劲才想出个土办法。
我先把表达式切成一个个小的部分,然后逐个比较,看哪些是一样的。
但是这个方法特别笨,效率也不高,后来看了些相关资料,才知道有更高效的算法去做这个事情。
循环优化也是很重要的一部分。
我曾经在优化一个循环计算的程序时就吃过亏。
我都没意识到循环里有些计算是可以提前放在循环外面做的。
比如说,计算圆的面积,公式里的圆周率要是在每次循环都重新定义计算就特傻,直接在循环外算一次用就行了。
这个道理说起来简单,但真正到复杂的代码里就容易忽略。
再就是函数内联,这方法我也算有点心得。
就好比你去办事,你不通过中间人,直接自己去办。
如果一个函数调用很频繁而且函数体又不是很复杂的话,把函数代码直接嵌入到调用处,而不是一直重复函数调用的那套流程,能节省不少时间。
我刚开始做的时候,不太确定哪些函数该内联,就一味地给小函数都做内联。
结果发现有些虽然函数体小但是调用非常复杂的函数做了内联后,反而使代码乱七八糟,更难读懂了。
编译原理优化
编译原理优化是指在编译过程中对程序进行优化,以提高程序的执行性能和效率。
优化可以在不改变程序的功能和语义的前提下,通过改变程序的结构和算法,以及使用各种编译技术来提高程序的执行速度和资源利用率。
在编译过程中,优化的主要目标是减少程序的执行时间和提高计算资源的利用效率。
为了达到这个目标,编译器可以对程序进行各种优化,包括代码重排、循环展开、常量传播、消除冗余代码等。
代码重排是一种常见的优化技术,它通过改变程序中指令的顺序,以提高指令的局部性和并行度。
例如,通过将频繁执行的指令放在一起,可以减少指令的Cache Miss次数,从而提高
程序的执行速度。
循环展开是一种针对循环结构的优化技术,它可以将循环中的多次重复计算的指令展开成多个相同的指令,从而减少循环的迭代次数,提高程序的执行效率。
常量传播是一种优化技术,它通过将程序中的变量替换为其常量值,以避免运行时的计算开销。
如果变量的值在程序中是不变的,那么在编译的过程中可以将其替换为常量,从而减少了变量的内存访问和计算开销,提高了程序的执行速度。
消除冗余代码是一种优化技术,它通过删除程序中没有实际作用的代码,以减少程序的执行时间和内存占用。
例如,如果一个变量在程序中没有被使用,那么可以将其从程序中删除,从而减少了内存的占用和程序的执行时间。
除了上述的优化技术之外,编译原理还有很多其他的优化技术,如代码传递优化、局部变量优化、全局变量优化等。
这些优化技术都可以通过改变程序的结构和算法,以及使用各种编译技术来提高程序的执行性能和效率。
1.与机器有关的代码优化有那些种类,请分别举例说明。
解答:与机器有关的优化有:寄存器优化,多处理优化,特殊的指令优化,无用的指令消除等四类。
冗余指令删除假设源程序指令序列a:=b+c; c:=a-d;编译程序为其生成的代码很可能是下列指令序列:MOV b, R0ADD c, R0MOV R0,aSUB d, R0MOV R0,c假如第四条指令没有标号,上述两个赋值语句在一个基本块内,则第四条指令是多余的,可删除。
特殊指令的使用例如,如果目标机器指令系统包含增1指令INC,对于i:=i+1的目标代码MOV i, R0ADD #1, R0MOV R0, i便可被代之以1条指令Inc i说明:优化的特点是每个改进可能会引发新的改进机会,为了得到最好的改进,一般可能需要对目标代码重复扫描进行优化。
2.设有语句序列a:=20b:=a*(a+10);c:=a*b;试写出合并常量后的三元式序列。
解答:该语句序列对应的三元式序列为:(1)(:=, 20,a)(2)(+, a, 10)(3)(*, a, (2) )(4)(:=, a, b)(5)(* a, b)(6)(:=, (5), c)合并常量后的三元式序列为:(1)(:=, 20,a)(2)(:=, 600, b)(3)(:=, 12000, c)3、试写出算术表达式a+b*c-(c*b+a-e)/(b*c+d)优化后的四元式序列。
解答:该表达式的四元式序列为:(1)(*,b,c,T1)(2)(+,a,T1,T2)(3)(*,c,b,T3)(4)(+,T3,a,T4)(5)(-,T4,e,T5)(6)(*,b,c,T6)(7)(+,T6,d,T7)(8)(/,T5,T7,T8)(9)(-,T2,T8,T9)可对该表达式进行删除公共子表达式的优化。
优化后的四元式序列为:(1)(*,b,c,T1)(2)(+,a,T1,T2)(3)(-,T2,e,T5)(4)(+,T1,d,T7)(5)(/,T5,T7,T8)(6)(-,T2,T8,T9)4.设有算术表示式(a*b+c)/(a*b-c)+(c*b+a-d)/(a*b+c)试给出其优化后的三元式序列。
《编译原理》教学大纲(供四年制计算机科学与技术(医药软件开发)专业使用)____________________________________________________________________一、前言本课程是计算机专业的重要专业课之一,主要介绍程序设计语言编译构造的基本原理和基本实现方法。
本课程主要讲授形式语言、有限自动机、自上而下和自下而上的语法分析、LR分析方法、属性文法和语法制导翻译、语义分析的蹭代码产生、存储器的动态分配与管理、符号表的组织与管理、优化问题、代码生成等内容。
本课程学生应掌握以下基本概念和原理,语言和文法、正规式、有限状态自动机、递归下降分析、算符优先分析、SLR 文法、代码生成、代码优化。
本课程的重点是突出基本概念、基本原理及算法,通过课堂教学与实践环节的训练,使学生掌握编译实现的基本方法和技术。
本课程的前导课程是计算机组成原理、数据结构、汇编语言程序设计、微机原理、操作系统原理等,并与程序设计语言等课程相关联。
本课程是考试课。
采用综合考核的考试方法,即在课程结束后一次性闭卷考试为主,并结合课堂提问、课后作业、上机作业等方面的考查,综合评定成绩。
本课程教学时数为:计算机科学与技术专业72学时,其中理论学时48,上机24学时。
教学采用课堂讲授法。
二、教学目的要求和内容第一章引论【目的要求】掌握编译的基本概念、编译过程概述、编译程序的结构了解编译程序与程序设计环境,编译程序的构造【教学内容】编译程序工作的基本过程及其各阶段的基本任务,编译程序总体框架。
【教学方法】课堂讲授第二章高级语言及其语法描述【目的要求】掌握程序语言定义、初等数据类型、数据结构熟悉高级高级语言的一般特性、程序结构、语句与控制结构掌握上下文无关文法,语法分析树与二义性。
【教学内容】上下文无关文法,程序语言定义参数传递。
【教学方法】课堂讲授第三章词法分析【目的要求】掌握词法分析器任务掌握词法分析器设计掌握正规表达式与有限自动机熟悉词法分析器自动生成。
第十章代码优化某些编译程序在中间代码或目标代码生成之后要对生成的代码进行优化。
所谓优化,实质上是对代码进行等价变换,使得变换后的代码运行结果与变换前代码运行结果相同,而运行速度加大或占用存储空间少,或两者都有。
优化可在编译的不同阶段进行,对同一阶段,涉及的程序范围也不同,在同一范围内,可进行多种优化。
一般,优化工作阶段可在中间代码生成之后和(或)目标代码生成之后进行。
中间代码的优化是对中间代码进行等价变换。
目标代码的优化是在目标代码生成之后进行的,因为生成的目标代码对应于具体的计算机,因此,这一类优化在很大程度上依赖于具体的机器,我们不做详细讨论。
另外依据优化所涉及的程序范围,又可分为局部优化、循环优化和全局优化三个不同的级别。
局部优化指的是在只有一个入口、一个出口的基本程序块上进行的优化。
循环优化对循环中的代码进行的优化。
全局优化是在整个程序范围内进行的优化。
本章重点:局部优化基本块的DAG表示第一节优化技术简介为了说明问题,我们来看下面这个例子,源程序是:P :=0For I :=1 to 20 doP :=P+A[I]*B[I];经过编译得到的中间代码如图10-1-1所示,这个程序段由B1和B2两个部分组成,B2是一个循环,假定机器按字节编址。
那么,对于这个中间代码段,可进行如下这些优化。
1、删除多余运算(删除公共子表达式)优化的目的在于使目标代码执行速度较快。
图10-1-1中间代码(3)和(6)中都有4*I的运算,而从(3)到(6)没有对I赋值,显然,两次计算机的值是相等的。
所以,(6)的运算是多余的。
我们可以把(6)变换成:T4 :=T1。
这种优化称为删除多余运算或称为删除公共子表达式。
2、代码外提减少循环中代码总数的一个重要办法是代码外提。
这种变换把循环不变运算,即其结果独立于循环执行次数的表达式,提到循环的前面。
使之只在循环外计算一次,上例中,我们可以把(4)和(7)提到循环外。
经过删除多余运算和代码外提后,代码变成图10-1-2。
算换成加法运算等等。
在图10-1-2的循环中,每循环一次,I的值增1,T1的值与I保持线性关系,每次总是增加4。
因此,我们可以把循环中计算T1值的乘法运算变换成在循环前进行一次乘法运算,而在循环中将其变换成加法运算。
变换后如图10-1-3所示。
4、变换循环控制条件图10-1-3的代码中,I和T1始终保持T1= 4 * I的线性关系,这样可以把(12)的循环控制条件I≤20变换成T1≤80,这样整个程序的运行结果不变。
这种变换称为变换循环控制条件。
经过这一变换后,循环中I的值在循环后不会被引用,四元式(11)可以从循环中删除,这就是我们的目的所在。
5、合并已知量与复写传播图10-1-3中,四元式(3)计算4 * I时,I必为1。
所以(3)中的4 * I的两个运算对象都是编码时的已知量,可在编译时计算出它的值,即(3)可变为T1= 4这种变换称为合并已知量。
图10-1-3中,(6)把T1的值复写到T4中,四元式(8)要引用T4的值,而(6)到(8)之间未改变T4和T1的值,则(8)改为T6 : =T5[T1],之后运算结果保持不变。
这种变换称为复写传播。
图10-1-3经过变换循环控制条件,合并已知量和复写传播等变换后,变为图10-1-4。
6、删除无用赋值在图10-1-4中,(6)对T4赋值,但T4未被引用;另外,(2)和(11)对I赋值,但只有(11)引用I。
所以,只要程序中其它地方不需要引用T4和I,则(6),(2)和(11)对程序的运行结果无任何作用。
我们称之为无用赋值,无用赋值可以从程序中删除,如图10-1-5所示(设(6)(2)(11)为无用赋值)。
比较图10-1-1和图10-1-5可看出,经过优化后的代码的执行效率提高了很多。
当然,实现这些优化的代价也是很大的。
第二节局部优化我们所说的局部优化是指基本块内的优化,所谓基本块,是指程序中一顺序执行语句序列,其中只有一个入口语句和一个出口语句。
执行时只能从其入口语句进入,从其出口语句退出。
对于一个给定的程序,我们可以把它划分为一系列的基本块。
在各基本块的范围内分别进行优化。
一、基本块的划分在介绍基本块的构造之前,我们先定义基本块的入口语句,所谓入口语句,严格地说来就是:1、程序的第一语句;或者,2、条件转移语句或无条件转移语句的转移目标语句;或者,3、紧跟在条件转移语句后面的语句。
有了入口语句的概念之后,我们就可以给出划分中间代码(四元式程序)为基本块的算法,其步骤如下:1、求出四元式程序中各个基本块的入口语句。
2、对每一入口语句,构造其所属的基本块。
它是由该入口语句到下一入口语句(不包括下一入口语句),或到一转移语句(包括该转移语句),或到一停语句(包括该停语句)之间的语句序列组成的。
3、凡未被纳入某一基本块的语句、都是程序中控制流程无法到达的语句,因而也是不会被执行到的语句,我们可以把它们删除。
我们仍以上一节所述的例子来说明,对于图10-1-1中的代码段,由规则1,语句(1)是入口语句,由规则2,语句(3)是入口语句。
由规则3,跟随语句(12)的语句是入口语句,这样,语句(1)和(2)构成一个基本块,语句(3)—(12)构成一个基本块,也即图10-1-1中的B 1和B 2。
二、 基本块的变换很多变换可作用于基本块而不改变它计算的表达式集合,这样的变换对改进代码的质量是很有用的。
有两类重要的局部等价变换可用于基本块,它们是保结构的变换和代数变换。
基本块的主要保结构变换是 1、删除公共子表达式 2、删除无用代码 3、重新命名临时变量 4、交换语句次序 对于1和2,我们在第一节已经讨论过,在此简单讨论一下3和4。
重新命名临时变量:假如有语句t : = b + c ,其中t 是临时变量。
如果把这个语句改为u : = b + c ,其中u 是新的临时变量,并且把这个t 的所有引用改成u ,那么基本块的运算结果不变。
交换语句次序:如果基本块有两个相邻的语句: t 1 : = b + c t 2 : = x + y当且仅当x 和y 都不是t 1,b 和c 都不是t 2时我们可以交换这两个语句的次序。
有许多代数变换可以把基本块计算的表达式集合变换成代数等价的集合。
其中有用的变换是那些可以简化表达式或用较快运算代替较慢运算的变换。
例如:x : = x + 0 或 x : = x * 1这样的语句可以从基本块中删除而不改变它计算的表达式集合,又如 x : = y * * 2的指数算符通常要用函数调用来实现,使用代数变换,这个语句可由快速、等价的语句x : = y * y 来代替。
第三节 基本块的DAG 表示这一小节介绍如何应用有向图来进行基本块的优化工作。
先将我们所需使用的DAG 作一说明。
在一个有向图中,我们称任一有向边n i →n j (或表示为有序对(n i ,n j ))中的结点n i 为结点n j 的前驱(父结),结点n j 为结点n i 的后继(子结)。
又称任一有向边序列n 1→n 2, n 2→n 3,…,n k —1→n k 为从结点n 1到结点n k 的一条通路。
如果其中n 1=n k ,则称通路为环路。
该结点序列也记为(n 1 ,n 2,…,n k )。
例如,图10-3-1中有向图的通路(n 2 ,n 2)和(n 3 ,n 4 ,n 3)就是环路。
如果有向图中任一通路都不是环路,则称该有向图为无环路有向图,简称DAG。
图10-3-2的有向图就是一个DAG。
在DAG中,如果(n1 ,n2,…, n k)是其中一条通路,则称结点n1为结点n k的祖先,结点n k为结点n1的后代。
我们这一节中要用到的有向图,是一种其结点带有下述标记或附加信息的DAG:1、图的叶结点,即无后继的结点,以一标识符(变量名)或常数作为标记,表示该结点代表该变量或常数的值。
如果叶结点用来代表某变量A的位置,则用addr(A)作为该结点的标记。
通常把叶结点上作为标记的标识符加上下标0,以表示它是该变量的初值。
2、图的内部结点,即有后继的结点以一运算符作为标记,表示该结点代表应用该运算符对其后继结点所代表的值进行运算的结果。
3、图中各个结点上可能附加一个或多个标识符,表示这些变量具有该结点所代表的值。
上述这种DAG可用来描述计算过程,又称描述计算过程的DAG。
在以下的讨论中,我们简称DAG。
下面讨论基本块的DAG表示与构造。
一个基本块,可用一个DAG来表示。
下面(图10-3-3)列出各种四元式及相对应的DAG 的结点形式。
图中n i为结点编号,结点下面的符号(运算符、标识符或常数)是各结点的标记,各结点右边的标识符是结点的附加标识符。
四元式DAG结点(0)A:=B(:=,B,—,A)(1)A:=op B(op,B,—,A)(2)A:=B op C(op,B,C,A)(3)A:=B [C](=[ ],B[C],—,A)(4)if B rop C goto (s) (jrop, B, C, (S))(5)D[C]:=B ([]=,B ,—,D[C])(6)goto(s) (j ,—,—,(S))图10-3-3 四元式与DAG 结点接着给出一种构造基本的DAG 算法。
假设DAG 各结点信息将用某种适当的数据结构来存放。
并设有一个标识符(包括常数)与结点的对应表。
NODE (A )是描述这种对应关系的一个函数,它的值或者是一个结点的编号n ,或者无定义。
前一个情况代表DAG 中存在一个结点n, A 是其上的标记或附加标识符。
我们把图10-3-3中各形式的四元式按其对应结点的后继个数分为四类。
其中,四元式(0)称为0型,(1)称为1型;(2)和(3)称为2型;(5)称为3型。
对于3型四元式,由于对数组元素赋值的情形需特殊考虑,因此暂不讨论,对四元式(6)也不涉及,下面是仅含0,1,2型四元式的基本块的DAG 构造算法。
首先,DAG 为空。
对基本块的每一四元式,依次执行:1、如果NODE (B )无定义,则构造一标记为B 的叶结点并定义NODE (B )为这个结点;如果当前四元式是0型,则记NODE (B )的值为n ,转4。
如果当前四元式是1型,则转2.(1)。
如果当前四元式是2型,则:(1)如果NODE (C )无定义,则构造一标记为C 的叶结点并定义NODE (C )为这个结点,(2)转2.(2)。
2、(1)如果NODE (B )的标记为常数的叶结点,则转2(3),否则转3.(1)。
(2)如果NODE (B )和NODE (C )都是标记为常数的叶结点,则转2.(4),否则转3.(2)。
(3)执行op B (即合并已知量),令得到的新常数为P 。
如果NODE (B )是处理当前四元式时新构造出来的结点,则删除它。