精华内容
下载资源
问答
  • 包括公共子表达式优化、常表达式优化和循环不变式优化
  • 编译原理过程简述及中间代码优化

    千次阅读 2017-09-28 17:21:23
    一、编译过程图示如下:词法分析作用:找出单词 。...二、中间代码优化所谓代码优化是指对程序代码进行等价(指不改变程序的运行结果)变换。程序代码可以是中间代码(如四元式代码),也可以是目标代码。

    一、编译过程图示如下:

    这里写图片描述

    词法分析作用:找出单词 。如int a=b+c; 结果为: int,a,=,b,+,c和;
    语法分析作用:找出表达式,程序段,语句等。如int a=b=c;的语法分析结果为int a=b+c这条语句。
    语义分析作用:查看类型是否匹配等。

    二、中间代码优化

    所谓代码优化是指对程序代码进行等价(指不改变程序的运行结果)变换。程序代码可以是中间代码(如四元式代码),也可以是目标代码。等价的含义是使得变换后的代码运行结果与变换前代码运行结果相同。优化的含义是最终生成的目标代码短(运行时间更短、占用空间更小),时空效率优化。原则上,优化可以在编译的各个阶段进行,但最主要的一类是对中间代码进行优化,这类优化不依赖于具体的计算机。

    优化目的:在不改变程序运行效果的前提下,对被编译的程序进行等价变换,使之能生成更加高效的目标代码。

    优化原则

    等价原则。经过优化后不应该改变程序运行的结果。
    等效原则。使优化后所产生的目标代码运行时间较短,占用的储存空间较小。
    合算原则。应尽可能以较低的代价取得较好的优化效果。

    附:

    循环优化代码外提
    将在循环入口设置基本块,把循环中的循环不变运算提入基本块,且提入部分必须为循环出口必经运算。如图:

    这里写图片描述

    PS:
    1、 代码优化就是对程序进行等价变换,以提高目标程序的效率,通常只对中间代码进行优化。通常包括控制流分析、数据流分析和变换三部分。
    2、 以程序的基本块为基础,基本块内的优化叫局部优化,跨基本块的优化为全局优化,循环优化是针对循环进行的优化,是全局优化的一部分。
    3、 公共子表达式的删除、复制传播、无用代码删除、代码外提、强度削弱和归纳变量删除等都是一些常用的针对局部或者全局的代码优化方法。

    展开全文
  • 优化其实可以在编译的各个阶段进行,但最主要的一类优化是在目标代码生成以前,对语法分析、语义分析后产生的中间代码进行优化。这是因为中间代码的形式不依赖于具体的计算机,它可以是三地址码的形式,所以相应的...

    代码优化概述.

    通过对程序进行等价变换,使得从变换后的程序出发,能够生成更加有效的目标代码,这种变换我们叫做优化。

    优化其实可以在编译的各个阶段进行,但最主要的一类优化是在目标代码生成以前,对语法分析、语义分析后产生的中间代码进行优化。这是因为中间代码的形式不依赖于具体的计算机,它可以是三地址码的形式,所以相应的对于中间代码的优化也不依赖于具体的计算机。另一类优化是在生成目标代码时进行的,它很大程序上依赖于具体的计算机。
    中间代码的优化中,有很多技术和手段可以应用。大局上来说,中间代码优化器的位置如下图所示:

    【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】

    在这里插入图片描述
    其中编译前端就是词法分析、语法分析、语义分析以及中间代码生成这些阶段。有的优化工作很容易实现,比如基本块内的局部优化。在一个程序运行时,相当一部分的时间是花在循环代码上的,因此基于循环的优化是极其重要的。而有的优化技术涉及对整个程序的控制流、数据流分析,其实现代价是相当高的。
    我们进行中间代码优化的目的是为了产生更加高效的代码,所以由代码优化器提供的,对代码的各种变化必须要遵循一定的原则,一切为了更高效的代码。

    • 等价原则:经过优化后不应该改变程序运行的结果。这是最不应该也不能破坏的原则,试问程序结果都不正确了,优化的意义何在?
    • 有效原则:优化后的代码运行快、存储空间需求小。这也正是我们优化代码的目的,从时空两个维度尽可能让代码效率高。
    • 合算原则:尽可能以较低的代价,取得较高的优化效果。

    优化可以从各个环节着手。首先,在源代码层面,程序员通过选择适当的高效算法,安排适当的实现语句来提高程序的效率。例如,归并排序肯定要比直接插入排序在绝大多数情况下的效率更高,执行时间更短吧;其次,在设计语义动作时,我们不仅可以考虑产生更加高效的中间代码,还可以在语义分析阶段为优化做一些预备工作。例如,为循环代码的begin和end对应的中间代码“打上标记”,这样有助于后续的控制流、数据流分析;代码的分叉处和交汇处(通常是条件判断语句控制)也“打上标记”,这样有助于识别程序流图中的直接前驱和直接后继。对于编译产生的中间代码,我们安排专门的优化阶段,进行各种等价变换,以提高代码的工作效率。而在目标代码这一层面,我们应该考虑如何有效地利用寄存器,如何选择指令以及如何进行窥孔优化等。

    窥孔优化,顾名思义,是一种很局部的优化方式,编译器仅仅在一个基本块或者多个基本块中,针对已经生成的代码,结合CPU自己指令的特点,通过一些认为可能带来性能提升的转换规则,或者通过整体的分析,通过指令转换,提升代码性能。别看这些代码转换很局部,很小,但可能会带来很大的性能提升。这个窥孔,你可以认为是一个滑动窗口,编译器在实施窥孔优化时,就仅仅分析这个窗口内的指令。每次转换之后,可能还会暴露相邻窗口之间的某些优化机会,所以可以多次调用窥孔优化,尽可能提升性能。

    中间代码优化全局大观.

    上面我们说完了代码优化的地位、原因以及目标,接下来我们通过一段中间代码实例以及它从最初到优化完成的过程,来展示、介绍中间代码优化具体做了哪些事。首先我们给出这段中间代码的最初状态:

    【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】

    在这里插入图片描述
    图中是中间代码的基本块程序流图展示,至于基本块如何划分,我们后面会给出算法,这里并不是问题的重点。我们是要看,究竟代码优化器,对这样的中间代码,做了怎样的等价变换。

    1.删除公共子表达式(Common Subexpressions Elimination).

    对于一个表达式E,如果它的值在前面已经计算过,并且在这之后E中变量的值并没有发生过改变(至于常量值更是无法改变了,不要ETC),那我们就称这样的表达式E为公共子表达式。对于这样的公共子表达式,我们可以避免对它的重复计算,而全部使用E中已经计算出的结果,称为删除公共子表达式,有时也称为删除多余运算(因为已经计算过了).

    ETC(Electronic Toll Collection),中文翻译是电子不停车收费系统,是高速公路或桥梁自动收费。通过安装在车辆挡风玻璃上的车载电子标签与在收费站 ETC 车道上的微波天线之间进行的专用短程通讯,利用计算机联网技术与银行进行后台结算处理,从而达到车辆通过高速公路或桥梁收费站无需停车而能交纳高速公路或桥梁费用的目的。

    看我们上面给出的中间代码实例,当中哪些是多余运算,或者说公共子表达式呢?着眼于B 5 _5 5基本块:

    T6:=4*i
    x:=a[T6]
    T7:=4*i
    T8:=4*j 
    T9:=a[T8]
    a[T7]:=T9
    T10:=4*j
    a[T10]:=x
    goto B2
    

    我们可以看到T6和T7这一组,以及T8和T10这一组一共两组临时变量都属于上面提到的公共子表达式。4*i的结果已经被计算了放在T6变量中,那么T7还有计算的必要吗,显然没有。所以针对这部分代码,我们可以修改如下:

    T6:=4*i
    x:=a[T6]
    T7:=T6
    T8:=4*j 
    T9:=a[T8]
    a[T7]:=T9
    T10:=T8
    a[T10]:=x
    goto B2
    

    修改之后我们发现,B 5 _5 5中只需要分别计算一次4*i4*j. 我们还可以在更大的范围内来考虑删除公共子表达式的问题。我们注意到B 2 _2 2中计算了4*i的值并且保存在T2中,而B 3 _3 3中计算了4*j的值,保存在T4中。并且最关键的是,在这两个地方计算出表达式的值之后,i和j的值一直都没有发生变化,是很标准的公共子表达式(多余运算)。所以B 5 _5 5中的中间代码可以变换为如下的形式:

    T6:=T2
    x:=a[T6]
    T7:=T6
    T8:=T4
    T9:=a[T8]
    a[T7]:=T9
    T10:=T8
    a[T10]:=x
    goto B2
    

    对于B 6 _6 6我们也进行一次同样的分析之后,我们可以将中间代码修改为下面这样:

    【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】

    在这里插入图片描述
    我们可以对比现在的中间代码和最初的中间代码,T1=4*n;T2=4*i;T4=4*j这三个公共子表达式(4*n、4*i、4*j是公共表达式)在B 5 _5 5和B 6 _6 6中被优化了,删除了多余运算。

    2.复写传播.

    上面的中间代码还可以进一步改进,我们还是着眼于B 5 _5 5来进行分析。T6=T2;x=a[T6]这两条语句中,T6的值并没有发生改变,一直是T2的值,因此可以直接将a[T2]的值赋给x,这种变换称为复写传播。

    复写传播(拷贝传播):某些变量的值并未被改变过便赋给其他变量,则可直接引用原值本身.

    通过复写传播的优化方法之后,我们可以将B 5 _5 5变换为如下中间代码:

    T6:=T2
    x:=a[T2]
    T7:=T2
    T8:=T4
    T9:=a[T4]
    a[T2]:=T9
    T10:=T4
    a[T4]:=x
    goto B2
    

    进一步分析发现,a[T2]的值曾经在B 2 _2 2中被T3=a[T2]计算过,所以x=a[T2]可以变换为x=T3,进而再一次应用复写传播,将a[T4]=x变换为a[T4]=T3;同样的,因为a[T4]在B 3 _3 3中被T5=a[T4]计算过,所以T9=a[T4]可以变换为T9=T5,从而也应用复写传播,将a[T2]=T9变换为a[T2]=T5,至此,B 5 _5 5中的代码变换成了下面的样子:

    T6:=T2
    x:=T3
    T7:=T2
    T8:=T4
    T9:=T5
    a[T2]:=T5
    T10:=T4
    a[T4]:=T3
    goto B2
    

    对B 6 _6 6也进行同样的分析(其实不难发现,B 5 _5 5和B 6 _6 6的代码几乎是对称的,这段代码是快速排序的代码),我们得到了下面的中间代码:

    【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】

    在这里插入图片描述
    复写传播的目的是使得对于某些变量的赋值,变得无用。我们很快就可以看到这一点。

    3.删除无用代码.

    对于进行了复写传播之后的B 5 _5 5块进行分析,可以发现变量x以及T6,T8,T9,T10这些临时变量在赋值符号的右边都没有出现过,这就意味着它们的值其实在整个B 5 _5 5块内自从被赋值之后,就没有使用过,因此这些变量的赋值对于程序的运行结果没有任何作用,有没有都是一样。但对于代码的执行效率来说就不是这样了,可以看出B 5 _5 5代码段其实是在一个循环块内的,很小的性能累赘乘以循环次数就是很大的效率阻碍了。我们可以删除对于这些变量的赋值代码,从而将B 5 _5 5转换为下面的代码:

    a[T2]:=T5
    a[T4]:=T3
    goto B2
    

    一下子代码的逻辑清晰明了了许多,之前的代码中那么多赋值颠来倒去,很难看出到底在做什么。删除无用代码之后,不仅效率提高了,代码也清新了许多(虽然我们很少会直接看到中间代码,但代码的清新意味着计算机执行时的快速与高效,这一点比较删除前后的代码,不难看出吧).
    同样地,我们对B 6 _6 6段也进行无用代码的删除,当中的变量x以及临时变量T11,T12,T13,T14,T15的赋值语句都属于无用代码,删除之后的代码如下:

    a[T2]:=v
    a[T1]:=T3
    

    至此我们可以给出,删除公共子表达式、复写传播和删除无用代码之后的中间代码形式:

    【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】

    在这里插入图片描述
    不难看出,前面所介绍的三种优化都是针对某一个基本块内部所作的优化,例如B 5 _5 5块内的优化就只在块内自己进行,那么下面我们要介绍的优化技术,都是涉及循环的优化。

    中间代码优化根据优化所涉及的程序范围分成:

    • 局部优化:在程序基本块内进行的优化;
    • 循环优化:在程序循环体内进行的优化;
    • 全局优化:在整个程序范围内进行的优化。

    4.代码外提.

    对于循环中的某些代码,如果在整个循环的过程中它产生的结果是不变的,就可以将这部分代码提到循环外去,以免每一次循环都要对这条代码进行运算。例如对于while(i<limit-1)...这样的代码,如果limit是一个在循环中没有改变的值,我们完全可以将limit-1提到循环外:
    t=limit-1
    while(i<t)...
    这种变换称为代码外提,但在我们这个实例中,循环过程中并没结果不变的代码,所以这一优化步骤跳过。

    5.强度削弱与删除归纳变量.

    这一优化步骤中我们着眼于基本块B 3 _3 3。循环每执行一次,j的值-1,而T4始终与j保持着T4=4*j的线性关系,因而每循环一次T4的值-4. 因此我们可以将循环中对于T4的乘法运算,变换为对T4的减法运算。因为计算机中对于加减法的运算比乘除法要快,所以这一技术称为强度削弱。同理对于B 2 _2 2中的T2变量,我们也可以进行这样的强度削弱。

    基本归纳变量——若循环中对 B 只有唯一的递归赋值 B:=B+C 且 C 为循环不变量,则称 B 为循环的基本归纳变量。
    归纳变量——若B为基本归纳变量,而A在循环中的定值可以化归为B的线性函数: A:=C1*B+C2(C1,C2为循环不变量),则称A 为归纳变量,并称 A与 B同族。

    根据这里给出的定义,显然i和j就是定义中的基本归纳变量,而T2和T4就是归纳变量。在我们对T2=4*i以及T4=4*j进行强度削弱之后,i和j除了被用于条件判断语句if i>=j goto B6控制跳转之外,不在其他地方被引用,因此我们完全可以删除这里的基本归纳变量i和j,将条件控制语句用T2和T4的值来完成:if T2>=T4 goto B6。所以完成了强度削弱和删除归纳变量之后的中间代码如下所示:

    【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
    在这里插入图片描述
    到这里我们完成了一次中间代码优化,并且了解了代码优化的过程中到底做了哪些事情,以及如何完成这些事情的细节。

    展开全文
  • 编制程序,完成局部优化过程中的基本块划分。给定一段代码,判定程序的入口语句,划分基本块,删除无用产生式和冗余节点。
  • 一、代码优化的阶段 ...二、中间代码优化的分类 从优化的种类来看,中间代码的优化可有如下分类: 局部优化 循环上的优化(所有优化方法效果最好的) 1. 循环不变体外提 循环中不因循环而改变的部分可以提到循环的

    一、代码优化的阶段

    欲提高源程序的运行速度,需要经过几个阶段的优化:

    1. 用户对源程序进行优化(和编译器无关,与coder设计的算法有关)
    2. 编译器前端对中间代码进行优化
    3. 编译器后端对目标代码进行优化

    两个编译器必须等价,编译的结果必须是正确的,即使有99.99%的可能性是不正确的,但是效率很好也不行,正确性是根本。

    二、中间代码优化的分类

    从优化的种类来看,中间代码的优化可有如下分类:

    1. 局部优化

    循环上的优化(所有优化方法效果最好的)
    1. 循环不变体外提
    循环中不因循环而改变的部分可以提到循环的外部
    2. 削减运算强度
    在一些循环上的计算转换成另一种计算方式实现,比如b=a*2 -> b=a+a
    基本块的优化(按照某些规则划分成基本块)
    1. 常表达式节省
    2. 公共子表达式节省

    1. 全局优化

    全局数据流分析,从而使优化的效果更好
    考虑基本块里如何优化,已经得到的值和变量的表,全局得到更多的信息进行分析。
    在局部优化的基础上进行全局优化才有更大的提高,否则直接进行全局优化的提升效果不明显。

    三、常见的编译优化的种类

    1. 数学上的优化

    情况一:(主要是在生成中间代码的时候会产生)

    (-,a,0,t1) (*,a,1,t2)
    //这种a+0,和a*1的没有太大意义的运算,这样的四元式可以删除
    //正常写普通的代码一般不会出现这种情况
    //但是在一些不明显的地方,比如计算数组中的元素a[i]时,用【i-(数组下界)】xsize
    //数组下界为0的时候和size=1的时候都会产生上面的四元式
    //可直接换但是还要将运算结果进行处理
    

    情况二:

    2*a->a+a
    a^2->a*a
    

    原则上说,可以不计算的直接删去其四元式,直接写出结果;高运算强度的可以转化成低运算强度的。

    2. 表达式短路问题

    在这里插入图片描述
    如果在计算一个表达式的时候可以不用将整个表达式的值都计算出来就可以确定表达式的结果,在这种情况下,后面的计算可以省略。
    或运算:如果计算出第一个表达式的结果是1可以直接返回 true
    与运算:如果计算出第一个表达式的结果是0可以直接返回false

    3. 常表达式的节省

    • 计算过程中有一个表达式是33.14,这个实际上是两个常数,他的结果我是可以计算出来的,这样我们在编译的过程中把33.14算出来,在目标程序的进行中就不用进行计算了。
    • 通过编译程序将可以计算出的结果直接计算出来然后将相应位置替换成计算出的结果。
    • 把有关工作交给编译程序进行可以提高生成的目标程序的运行效率,编译程序可以稍微慢些的工作,因为最后看的还是生成的目标程序的运行效率。

    4. 公共子表达式节省(消除重复操作)

    在这里插入图片描述

    • 所谓公共子表达式即两个子表达式的计算结果是一样的,不需要重复的进行计算,第一次计算出来之后,后面的计算可以利用前面的计算结果。例如前面有表达式ab,后面又有表达式也是ab,而且在第二个表达式和第一个表达式中间,a和b的值没有被改变过,那第二个a*b表达式实际上是可以不用计算的,只需要把前面的计算结果拿过来就可以达到计算的目的。
    • 这种情况在下标变量的计算中体现的非常明显,比方说a[i][j]和a[i][k],下标变量的计算先计算a[i]的地址然后计算j,然后计算a[i][j]的地址,a[i][k]也是一样先计算a[i]的地址在根据k计算a[i][k]的地址,所以前面关于a[i]的计算结果是完全一样的。这样就可以不用重复计算,利用前面的结果达到计算目的。

    5. 循环不变量式外提

    在这里插入图片描述
    循环不变式外提,在循环中,如果有一个表达式的值在循环中不会改变,就需要把它提到循环体的外面去。比方说有一个双重循环1-100和1-100,那么整个两重循环计算下来就是1万次,比方说里面有一个ab的表达式,在循环过程中ab的值如果不改变,那他就重复计算了9999次,假如把ab提到循环体的外面只需要计算1次,在循环里只需要用到它的计算结果。所以这样的表达式在循环体里面是不变的,我们要把他提到循环体的外面,从而提高我们的执行效率,这种效率的提高是最高的。特别是计算量很大的情况下,比方说有多重循环,这种循环不不变式外提可以大大提高程序的执行效率,这种提高的效率大概可以提高十几倍甚至是几十倍,串式程序的并行处理很大的一个研究重点,都是放在循环的并行计算上,比方说1~1000次的循环把它分成段,在不同的处理机上进行执行,从而提高程序的执行效率

    6. 削减程序的运算强度

    在这里插入图片描述
    削减程序的运算强度。这个也通常是针对循环的,一种特例的情形,比方说在程序设计语言中通常来说,一般的程序循环有三种方式分别是:
    1)for循环(有的语言中也称作是步长式的循环),循环形式是for i=e1 to e2 ;step e3{} 也就是说它的执行是i开始获取一个循环初值e1,然后每次循环i都加上e3的值,一直当i大于e2就结束循环,比方说for1=1to 100;step 1,就是1-100步长是1执行100次。
    2)while循环,“当型循环”()
    3)直到型循环 do until ~~的形式

    7. 寄存器优化

    在这里插入图片描述

    生成目标代码的时候一定是和目标机相关的,对于目标机来说提供多少个寄存器,比方说提供了8个,当目标程序真正执行的时候,这8个寄存器是如何被分配的,这是一个问题。简单的说假如说寄存器有空闲的时候,分配方案比较简单,空闲的就分配,这个没问题,什么时候会有问题呢?
    有这样几种情况:
    要用寄存器的时候,是否有空闲的寄存器,如果没有空闲的怎么办?需要剥夺一个寄存器,如何来剥夺?类似的还有内存和外存进行淘汰页的算法是类似的,只不过这个是在更高一级的层面上,寄存器和内存间的变换。这是寄存器分配优化

    8. 消除无用语句,消除冗余代码

    在这里插入图片描述

    消除无用语句,消除冗余代码,假如一个条件语句,if e s1 else s2 ,如果表达式E的值是一个true,那么s2是不可能用到了,所以这个命令就可以删掉了,这个命令也直接可以用s1来替换掉,当然这是一种特殊的情形,产生冗余代码实际上也是这么产生的

    9. 中间变量优化

    在这里插入图片描述

    中间变量的优化,生成中间代码的时候会产生大量的临时变量,大量的临时变量的特点一般都是产生之后使用一次就会不再用了,如果是说有一些临时变量和另外一些临时变量之间没有交集的话,不需要为每一个临时变量都分配独立的存储单元。最简单的方式,把产生临时变量一直到使用临时变量这个区域比方说把它定义为临时变量的活动区,假如两个临时变量的活动区不相交,实际上他们可以共用同一个存储单元,那么这种优化实际上就是临时变量的存储空间上的优化

    10. 目标代码优化

    在这里插入图片描述

    是目标代码的优化,可以通过确定目标代码来减少目标程序的指令个数来提高目标程序的执行效率,比方说有一个变量的值在寄存器里,运算出来的中间结果在寄存器里,假如直接让他参与运算之后,就不用往内存中存了,什么时候需要存,什么时候不存,什么时候需要把它放在寄存器里,这就是需要对目标程序进行的一种优化,根据使用情况来进行。

    11. 全局优化

    在这里插入图片描述
    总而言之这种优化还有很多,比方说全局的数据流分析和全局的优化,因为前面考虑实现的时候由于优化的技术可能比较复杂,都是在局部区做的,比方说一个基本块上做的,要想做到全局的需要对全局的程序做全局的数据流分析,这样的话可能就更复杂了,如果没有特殊需求,一般来说是不做这样的优化的:
    第一个原因就是编译代价太大,因为要做各种各样的分析,导致编译代价很大。
    第二就是提高的效率也并不是十分的明显。

    如果说没有特殊的需求,前面做的局部优化已经能达到想要的效果,全局优化就不用做了,特殊需求的时候才想办法处理。

    ⚠️优化中要注意的一个问题就是优化是在保证正确的情况下进行的,任何一个程序的优化也不能做到最优,而是在一定程度上来提高程序的执行效率。所以优化的过程一定是在保证程序正确的前提下来进行的。

    四、基本块

    (一)基本块的定义

    在这里插入图片描述

    • 基本快:是一个命令的序列,每一条命令都是按照顺序来执行的,进入基本块只能从第一条命令进入,退出基本块只能从最后一条命令退出,也就是说基本块是一个整体,要执行的话就全都执行从头到尾的顺序执行,这样一个特性对于程序进行分析或者是对中间代码进行分析有什么好处呢?对程序基本块中变量的性质是可判定的,假如不是基本块的话,x=10;L y=x+1;如果说只能从x=10这条语句顺序执行的话,y=x+1,这个x就是一个已知的,就是10,y就是11.但是假如说它有另外一个入口,其他地方有一个goto L,就不能判断x的值是不是10,因为外面有一个转移,比方说x=1; goto L x的值就不一定是10了,不是基本块的话程序的性质就不太好判断
    • 假如说是一个基本块都是顺序往下执行的话,那么每一个变量的值是怎么获取的、怎么传播出来的,就可以判断出来,这对我们的处理就非常有意义。因此在基本块上做优化使得问题变的相对简单了。

    (二)基本块的划分原则

    在这里插入图片描述

    1. 进入一个程序,(整个程序的第一条)四元式就是一个基本块的入口四元式;
    2. 遇到一个转移性的四元式,这条转移性的四元式是结束了一个基本块,它的后续四元式就可以作为下一个基本块的第一条四元式;
    3. 遇到一个定位性的四元式或者是标号性的四元式,就结束一个基本块,并作为下一个基本块的第一条四元式;
    4. 那么遇到一些函数的结束标示,就结束当前的四元式。例如p=&x; *p=5; y=x+x; 因为p是间接取址的,所以要结束这个标准块,否则也还是不知道如何来判断。

    在这里插入图片描述
    比方说goto还有(then t1,- ,-)目标程序运行到这的时候要判断t1是真是假,如果是假的话就要跳过t1,所以一定是要产生一个跳转指令的,还比如else这种,也就是说s1执行完一定要产生一个跳转指令跳过s2。
    在这里插入图片描述
    比方说循环指令中的while四元式,当循环体循环结束之后要产生一个跳转指令,要转移到前面重复计算的表达式的位置,假如没有这样一个四元式, 就没有办法确定转移到什么地方。这样的四元式就起到一个定位型四元式,还有比方说endif等等。还有对间接变量的赋值,也表示一个基本块的结束,这是基本块划分的原则,总的来说最关键的两点就是遇到转移型四元式和定位型四元式的开始
    在这里插入图片描述
    在这里插入图片描述

    五、常表达式节省

    在编译过程中能够计算出值的表达式,就在中间代码这级给它计算一个结果,这样就不需要在目标程序执行的时候进行计算了。
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    1. 首先构造一个表,即变量的值表,表中元素是一个二元组,表项左部是一个变量名或者是一个临时变量,右边是已知它的值是什么,就填到这里。
    2. 以后的优化算法也就比较简单,通常来说是这样做的:进入到一个基本快的时候把这个表清空,遇到一个运算型的四元式,比方说有算符 a b t1,首先看一下a和b是不是常量,如果是常量当然可以进行计算,就把t1填到表里。如果是变量的话,就要去表中查一下有没有变量对应的值,如果有值,就把这个变量用值来替换然后来看看可不可以进行计算,能计算就进行计算,不能计算替换完了之后,值就放到四元式中。
    3. 如果遇到一个赋值型四元式,比方说b=a,就要到表里去查a有没有值,如果a是已知的,就把b填到表里,把a的值取出来填给b。按照这样顺序进行计算,能够算出常量值的四元式就被节省掉了。这里要注意,运算型的四元式实际上第一步,严格来说,用常量值替代运算符,比方说3*3.14 t1,就算出t1的结果,以后用到t1的时候就从表里取t1的值就可以了
    4. 中间可以夹杂着用常量定值表对原有四元式进行替换,如果四元式都变成常量的就删除,否则就留着

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 一个简单的编辑器 编译原理课设 对简单的程序进行语义分析并将中间代码生成
  • 编译原理代码优化

    万次阅读 多人点赞 2017-12-18 10:43:49
    编译原理出于代码编译的模块化组装考虑,一般会在语义分析的阶段生成平台无关的中间代码,经过中间代码级的代码优化,而后作为输入进入代码生成阶段,产生最终运行机器平台上的目标代码,再经过一次目标代码级别的...

    前面介绍完了词法分析、语法分析和语义分析,以及各阶段如何利用符号表来实现代码合理性确认以及代码地址拉链式回填等工作。编译原理出于代码编译的模块化组装考虑,一般会在语义分析的阶段生成平台无关的中间代码,经过中间代码级的代码优化,而后作为输入进入代码生成阶段,产生最终运行机器平台上的目标代码,再经过一次目标代码级别的代码优化(一般和具体机器的硬件结构高度耦合,复杂且不通用)。故而出于理解编译原理的角度考虑,代码优化一般都是以中间代码级代码优化手段作为研究对象。


    代码优化按照优化的代码块尺度分为:局部优化、循环优化和全局优化。即
    1. 局部优化:只有一个控制流入口、一个控制流出口的基本程序块上进行的优化;
    2. 循环优化:对循环中的代码进行的优化;
    3. 全局优化:在整个程序范围内进行的优化。

    1. 常见的代码优化手段

    常见的代码优化技术有:删除多余运算、合并已知量和复写传播,删除无用赋值等。采用转载自《编译原理》教材中关于这些优化技术的图例快速地展示下各优化技术的具体内容。

    针对目标代码:

    P := 0
    for I := 1 to 20 do 
        P := P + A[I]*B[I] 

    假设其翻译所得的中间代码如下



    1. 删除多余运算
    分析上图的中间代码,可以发现 (3)和式 (6)属于重复计算( 因为I并没有发生变化),故而式 (6)是多余的,完全可以采用 T4∶=T1代替。

    2. 代码外提
    减少循环中代码总数的一个重要办法是循环中不变的代码段外提。这种变换把循环不变运算,即结果独立于循环执行次数的表达式,提到循环的前面,使之只在循环外计算一次。针对改定的例子,显然数组A和 B的首地址在计算过程中并不改变,则作出的改动如下

    3. 强度削弱
    强度削弱的本质是把强度大的运算换算成强度小的运算,例如将乘法换成加法运算。针对上面的循环过程,每循环一次,I的值增加1T1的值与I保持线性关系,每次总是增加4。因此,可以把循环中计算T1值的乘法运算变换成在循环前进行一次乘法运算,而在循环中将其变换成加法运算。

    4. 变换循环控制条件
    IT1始终保持T1=4*I的线性关系,因此可以把四元式(12)的循环控制条件I≤20变换成T1≤80,这样整个程序的运行结果不变。这种变换称为变换循环控制条件。经过这一变换后,循环中I的值在循环后不会被引用,四元式(11)成为多余运算,可以从循环中删除。变换循环控制条件可以达到代码优化的目的。

    5. 合并已知量和复写传播
    四元式(3)计算4*I时,I必为1。即4*I的两个运算对象都是编码时的已知量,可在编译时计算出它的值,即四元式(3)可变为T1=4,这种变换称为合并已知量。

    四元式(6)T1的值复写到T4中,四元式(8)要引用T4的值,而从四元式(6)到四元式(8)之间未改变T4T1的值,则将四元式(8)改为T6∶=T5[T1],这种变换称为复写传播。

    6. 删除无用赋值
    (6)T4赋值,但T4未被引用;另外,(2)(11)对I赋值,但只有(11)引用I。所以,只要程序中其它地方不需要引用T4I,则(6)(2)(11)对程序的运行结果无任何作用。我们称之为无用赋值,无用赋值可以从程序中删除。至此,我们可以得到删减后简洁的代码

    2. 基本块内的局部优化

    1. 基本块的划分
      入口语句的定义如下:
      ① 程序的第一个语句;或者,
      ② 条件转移语句或无条件转移语句的转移目标语句;
      ③ 紧跟在条件转移语句后面的语句。
    有了入口语句的概念之后,就可以给出划分中间代码(四元式程序)为基本块的算法,
      其步骤如下:
      ① 求出四元式程序中各个基本块的入口语句。
      ② 对每一入口语句,构造其所属的基本块。它是由该入口语句到下一入口语句(不包括下一入口语句),或到一转移语句(包括该转移语句),或到一停语句(包括该停语句)之间的语句序列组成的。
      ③ 凡未被纳入某一基本块的语句、都是程序中控制流程无法到达的语句,因而也是不会被执行到的语句,可以把它们删除。

    2. 基本块的优化手段
    由于基本块内的逻辑清晰,故而要做的优化手段都是较为直接浅层次的。目前基本块内的常见的块内优化手段有:
    1. 删除公共子表达式
    2. 删除无用代码
    3. 重新命名临时变量 (一般是用来应对创建过多临时变量的,如t2 := t1 + 3如果后续并没有对t1的引用,则可以t1 := t1 + 3来节省一个临时变量的创建
    4. 交换语句顺序
    5. 在结果不变的前提下,更换代数操作(x∶=y**2是需要根据**运算符重载指数函数的,这是挺耗时的操作,故而可以用强度更低的x∶=y*y来代替
    根据以上原则,对如下代码进行优化

    t1 := 4 - 2
    t2 := t1 / 2 
    t3 := a * t2
    t4 := t3 * t1
    t5 := b + t4
     c := t5 * t5

    给出优化的终版代码

       t1 := a + a
       t1 := b + t1
        c := t1 * t1

    显然代码优化的工作不能像上面那样的人工一步步确认和遍历,显然必然要将这些优化工作公理化。而一般到涉及到数据流和控制流简化的这种阶段,都是到了图论一展身手的时候。

    3. DAG(无环路有向图)应用于基本块的优化工作
    在DAG图中,通过节点间的连线和层次关系来表示表示式或运算的归属关系:
    ① 图的叶结点,即无后继的结点,以一标识符(变量名)或常数作为标记,表示这个结点代表该变量或常数的值。如果叶结点用来代表某变量A的地址,则用addr(A)作为这个结点的标记。
    ② 图的内部结点,即有后继的结点,以一运算符作为标记,表示这个结点代表应用该运算符对其后继结点所代表的值进行运算的结果。
    (注:该部分内容转载自教材《编译原理》第11章DAG无环路有向图应用于代码优化)

    DAG构建的流程如下

    对基本块的每一四元式,依次执行:
      1. 如果NODE(B)无定义,则构造一标记为B的叶结点并定义NODE(B)为这个结点;
      如果当前四元式是0型,则记NODE(B)的值为n,转4。
      如果当前四元式是1型,则转2.(1)。
      如果当前四元式是2型,则:(Ⅰ)如果NODE(C)无定义,则构造一标记为C的叶结点并定义NODE(C)为这个结点,(Ⅱ)转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)是处理当前四元式时 新构造出来的结点,则删除它。如果NODE(P)无定义,则构造一用P做标记的叶结点n。置NODE(P)=n,转4.。
      (4) 执行B op C(即合并已知量),令得到的新常数为P。如果NODE(B)或NODE(C)是处理当前四元式时新构造出来的结点,则删除它。如果NODE(P)无定义,则构造一用P做标记的叶结点n。置NODE(P)=n,转4.。
      3.
      (1) 检查DAG中是否已有一结点,其唯一后继为NODE(B),且标记为op(即找公共子表达式)。如果没有,则构造该结点n,否则就把已有的结点作为它的结点并设该结点为n,转4.。
      (2) 检查DAG中是否已有一结点,其左后继为NODE(B),右后继为NODE(C),且标记为op(即找公共子表达式)。如果没有,则构造该结点n,否则就把已有的结点作为它的结点并设该结点为n。转4.。
      4.
      如果NODE(A)无定义,则把A附加在结点n上并令NODE(A)=n;否则先把A从NODE(A)结点上的附加标识符集中删除(注意,如果NODE(A)是叶结点,则其标记A不删除),把A附加到新结点n上并令NODE(A)=n。转处理下一四元式。

    说着很复杂,下面看一个案例

    (1) T0∶=3.14
    (2) T1∶=2 * T0
    (3) T2∶=R + r
    (4) A∶=T1 * T2
    (5) B∶=A
    (6) T3∶=2 * T0
    (7) T4∶=R + r
    (8) T5∶=T3 * T4
    (9) T6∶=R - r
    (10) B∶=T5 * T6

    其DAG图的构建过程如下

    通过DAG图可以发现诸多的优化信息,如重复定义、无用定义等,则根据上图的DAG图可以构建最后的优化代码序列

      (1) S1∶=R+r
      (2) A∶=6.28*S1
      (3) S2∶=R-r
      (4) B∶=A *S2

    3.循环优化

    根据上面基本块的定义,我们将诸多基本块组装在一起,构建成程序循环图,如针对下面这个例子

      (1) read x
      (2) read y
      (3) r∶=x mod y
      (4) if r=0 goto (8)
      (5) x∶=y
      (6) y∶=r
      (7) goto (3)
      (8) write y
      (9) halt

    则按照上面基本块的划分,可以分成四个部分,四个部分的控制流分析可知可以得到一个循环图

    循环块最主要的特点是只有一个数据流和控制流入口,而出口可能有多个。循环优化的主要手段有:循环次数无关性代码外提、删除归纳变量和运算强度削弱。关于这三种手段的理解可以借助此前的描述进行类比,基本并无太多差异。

    展开全文
  • 编译原理试验报告 课题:中间代码优化;表达式语法分析等 包含所有的报告以及C++与程序
  • 中间代码优化(与机器无关) 目标代码优化(与机器有关)   优化分类 局部优化 循环优化 全局优化   优化技术 删除公共子表达式:t1 = 4 * i和t2 = 4 * i的右侧是公共的,优化为t1 = 4 * i和t2 = t1 ...
  • 也正是由于这部分代码序列可能会被反复执行,所以在进行中间代码优化时应着重考虑循环优化,这对提高目标代码的效率起到很大的作用。为了进行循环优化,首先需要确定的是程序流图中哪些基本块构成一个循环。按照结构...
  • 编译原理 中间代码表示

    千次阅读 2018-10-20 23:03:31
    将抽象层次逐渐降低,有的优化只能在特定的中间表示上才行。 三地址码: 不绑定特定的指令集,是抽象的类型 每个三地址码只完成一条指令,没有复合的情况出现。 如何生成三地址码?     ...
  • 对于一个给定的程序,我们可以把它划分为一系列的基本块。...局限于基本块范围内的优化称为基本块内的优化,或者称为局部优化。所谓基本块,是指程序中一个顺序执行的语句序列,其中只有一个入口和一个出口。 ...
  • (1)便于进行与机器无关的代码优化; (2)使编译程序改变目标机更容易;易于编译器的移植 (3)使编译程序的结构在逻辑上更为简单明确,以中间语言为界面,编译前端和后端的接口更清晰。 中间语言的形式:...
  • 编译原理中间代码生成

    千次阅读 2020-04-07 12:35:44
    如果不生成中间代码而是直接生成机器语言或者汇编语言形式的目标代码,优点是编译时间短,缺点是目标代码执行效率和质量都比较低,移植性差。 为什么不直接翻译成机器码呢,而多此一举生成中间代码再转换?(代码的...
  • 编译原理实验指导:词法分析,语法分析以及中间代码生成及优化。使用Linux下的flex,bison和gcc实现。指导书很详细,每个部分一份指导书。
  • 代码优化_11 优化可生成()的目标代码。A. 运行时间较短B. 占用存储空间较小C. 运行时间短但占用内存空间大D. 运行时间短且占用存储空间小 2 基本块内的优化为 ( )。A. 代码外提,删除归纳变量B. 删除多余运算,...
  • 编译原理中间代码(一)

    万次阅读 多人点赞 2017-12-05 15:08:56
    在编译器的分析-综合模型中,前端对源程序进行分析并产生中间表示,后端在此基础上生成目标代码。...和中间代码相关的内容包括中间代码表示、静态类型检查和中间代码生成,本文将讨论关于中间代码表示的内容。
  • 编译原理及实现技术:24.中间代码优化2.ppt
  • 编译原理及实现技术:23.中间代码优化1.ppt
  • 编译原理实验源码.zip

    2020-12-07 21:15:23
    华中科技大学编译原理实验源码一到四,运行makefile文件即可,不过电脑应该先安装c编译器。 实验一:词法语法分析器的设计与实现; 实验二:符号表管和语义检查...实验三:中间代码生成和优化; 实验四:目标代码生成。
  • 要求中间代码具有如下特性,以便于编译器的开发移植和代码的优化 优点:便于语法制导翻译 既与机器指令的结构相近,又与具体机器无关。 中间代码的主要形式:树、后缀式、三地址码等 后缀式 也被称为逆波兰...
  • 编译原理--中间代码生成

    千次阅读 2020-01-08 15:25:16
    文章目录基础DAG三地址代码问题声明语句的翻译表达式和赋值语句的翻译控制流翻译布尔表达式的翻译switch 语句的翻译过程调用的翻译回填 基础 DAG 语法树是一种图形化的中间表示。但语法树中,公共子表达式每出现一次...
  • 编译原理代码生成

    万次阅读 2017-12-18 16:13:41
    前面提到了经过了词法分析->语法分析->语义分析->中间代码优化,最后的阶段便是在目标机器上运行的目标代码的生成了。目标代码生成阶段的任务是:将此前的中间代码转换成特定机器上的机器语言或汇编语言,这种转换...
  • 与机器无关的优化一般在中间代码上进行。 代码优化也可分为局部优化、 循环优化和全局优化: 局部优化指的是在只有一个入口、 一个出口的基本程序块上进行的优化。 循环优化是对循环中的代码进行的优化,在...
  • 以基本块为单位,进行运算上的推导优化。堪称妙!原来编译器这么强大!
  • 编译原理 词法分析 语法分析 优先分析表 中间代码生成
  • 编译原理-中间代码的生成

    千次阅读 2020-03-30 19:17:46
    一、中间代码简介 中间代码应具备的特性: 1)便于语法制导翻译 2)既与机器指令的结构相近,又与具体机器无关 使用中间代码的好处: 1)一是便于编译器程序的开发和移植 2)二是代码进行优化处理 中间代码的...
  • 另一个称为执行表,它按照三元式的执行顺序,依次列出相应各三元式在三元式表中的位置,也就是说我们用一个三元式表连同执行表来表示中间代码。通常我们称这种表示方法为间接三元式。 3.三元式、四元式、间接三元式...
  • 中间代码优化;优化技术简介常数合并;优化技术简介常数传播;优化技术简介代数简化;优化技术简介代数简化;优化技术简介降低运算强度;优化技术简介复写传播;基本块是指程序中一顺序执行的语句序列 其中只有一个入口...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 50,079
精华内容 20,031
关键字:

编译原理中间代码优化