【编译原理】中间代码优化(三) 循环优化
文章目录
- 循环优化概述.
- 计算必经节点集.
- 循环查找算法.
- 1.查找回边.
- 2.查找循环.
- 代码外提.
- 强度削弱.
- 删除归纳变量.
循环优化概述.
什么叫做循环?循环就是程序中那些可能反复执行的代码序列。也正是由于这部分代码序列可能会被反复执行,所以在进行中间代码优化时应着重考虑循环优化,这对提高目标代码的效率起到很大的作用。为了进行循环优化,首先需要确定的是程序流图中哪些基本块构成一个循环。按照结构程序设计思想,程序员在编程时应使用高级语言所提供的结构性的循环语句来编写循环。而由高级语言的循环语句所形成的循环,是不难找出的。对于循环中的代码,可以实行代码外提、强度削弱和删除归纳变量等优化操作。一个中间代码序列的程序流图如下所示:
我们不难看出,其中B2_22和B3_33分别构成一个循环,{B2_22,B3_33,B4_44,B5_55}构成一个范围更大的循环。而我们要进行循环优化,首先需要知道循环在哪里,接下来,我们就一步步给出,整个循环优化的过程。
计算必经节点集.
【定义】:若从首结点出发到达结点Nj_jj的每一条通路都必须经过结点Ni_ii,那么我们称Ni_ii是Nj_jj的必经节点,记为Ni_ii dom Nj_jj,其中dom是dominate的简写。那么Nj_jj所有必经节点的集合,就是Nj_jj的必经节点集,记为D(Nj_jj).
计算程序流图中必经节点集的算法也是一个不动点算法,通过每一次的迭代来计算一个结点的必经节点集。当程序流图中所有结点的迭代计算结果都不发生改变时,说明已经获得了最终的结果,算法宣布结束。下面我们给出计算必经节点集的算法:
这里我们也给出一个例题,作为计算必经节点集算法的收尾:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
需要注意的是,该算法并不像图中那样,一次迭代计算就可以确定结果,而是需要再一次迭代确认了所有的结点都没有发生变化,才能确定最终结果的。
循环查找算法.
1.查找回边.
【定义】如果一个程序流图中存在有向边<Ni_ii→Nj_jj>,并且Nj_jj∈D(Ni_ii),那么我们称有向边<Ni_ii→Nj_jj>是一条回边。
依旧是我们前一部分给出的实例,考察其中的回边:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
2.查找循环.
求出了程序流图中的回边之后,我们就可以基于回边来查找循环。具体的方法是,若<Ni_ii→Nj_jj>是一条回边,那么该回边构成的循环中的结点包括:Ni_ii和Nj_jj以及所有不经过Ni_ii能够到达Nj_jj的结点。我们以上图中求出的三条回边为例,6→6确定的循环中只包括结点6;7→4确定的循环中包括结点有{4,7,5,6};4→2确定的循环中包括的结点有{2,4,3,5,6,7}。
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
代码外提.
至此我们已经完成了查找循环包含结点的工作,接下来就是要对循环体中的中间代码进行优化了。循环中的某些代码虽然随着循环反复地执行,但其中某些运算的结果并没有发生改变,例如循环中有形如A:=B op C的代码,并且如果B和C都是常数,或者到达它们的定值点都在循环外,那么不管循环多少次,每次计算出来得到的B op C的结果都是不变的,对于这样的不变运算,我们完全可以将其外提到循环以外,避免其随着循环多次计算。如此一来,程序的结果没有发生变化,但程序的运行速率却得到了一定程度的提高,这就是代码外提。
【定义】所谓变量A在程序中某一点d的定值到达了另一点u,或者说变量A的定值点d达到另一点u,指的是程序流图中从d有一条通路到达u,并且通路上没有A的其它定值点。
实行代码外提时,我们在循环的入口结点之前建立一个新结点(基本块),称为循环的前置结点,该前置结点以循环的入口结点为其唯一后继。并且原来流图中从循环外引到循环入口结点的有向边,都改为引到该前置结点。如下图所示:
我们考虑的循环结构,其入口结点都是唯一的,从而其前置结点也是唯一的,我们后续进行代码外提时所有外提的代码都将提到前置结点中。下面一段Pascal源程序,是我们叙述代码外提的第一个例子:
for I:=1 to 10 doA[I,2*J]:=A[I,2*J]+1
它对应的中间代码序列如下:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
- 考察序号为3和7的语句,因为循环中并没有J的定值点,所以其中J所有引用的定值点都在循环外,从而3和7都是循环不变运算;
- 考察序号为6和10的语句,分配给数组A的首地址addr(A)的值并不会随着循环的一次次执行而改变,所以6和10也都是循环不变运算。
所以我们可以做出这样的判断:3与7、6与10都可以外提到该循环的前置节点中,进行了代码外提之后的中间代码序列如下所示:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
那么问题又来了,是不是在任何情况下,都可以将循环不变运算进行外提呢?我们看第二个例子:
从程序流图中我们不难看出,{B2_22,B3_33,B4_44}构成了一个循环,并且B2_22是入口结点,B4_44是出口结点。
【定义】出口结点是指循环中具有如下性质的结点:从该结点有一条有向边引到循环外的某结点。
图中我们也不难看出,基本块B3_33中的I:=2
语句是循环不变语句,那么我们是否可以直接将这一语句外提到循环以外的前置结点B2′_2'2′呢?我们暂且认为可以这样做,外提之后的程序流图如下所示:
我们看外提了I:=2
这一语句的程序流图,执行到基本块B5_55时,变量I
的值总会是2,从而J的值也会是2.但我们需要注意的是,在原始的、没有进行I:=2
外提的程序流图中,B3_33并不是B4_44的必经结点。我们在原始流图中考虑X=30、Y=25的情况,这样的情况下B3_33是不会被执行的,所以执行到B5_55时变量I
的值应该是1,从而J的值也应该是1而非2.那么很显然,我们进行了I:=2
外提的程序已经改变了程序运行的结果,这必然是不允许的。问题到底出在什么地方呢?其实前面已经点出,B3_33并不是循环出口结点B4_44的必经节点,再直白一点说,B3_33中的代码并不一定会对B4_44中的代码起到作用,但如果我们将其外提到循环的前置结点,那么该外提的语句必然是会对B4_44产生影响的,这就导致了程序运行结果被改变的风险,我们并不允许这样的风险存在。
所以从这个例子中我们可以看到,当一个循环不变运算要外提到循环的前置结点时,要求该循环不变运算的语句所在的结点是循环的所有出口结点的必经结点。
另外,我们注意到,如果循环中变量I
的所有引用点都是B3_33中I
的定值点所能到达的,I
在循环中不再有其它的定值点,并且出循环之后也不会再引用变量I
的值(即在循环外的循环后继结点入口,变量I
不是活跃的),那么即使B3_33不是B4_44的必经结点,还是可以将I:=2
外提到循环的前置结点,因为这样做并不会改变程序的结果。
再一个例子,如果我们将上述程序流图中的基本块B2_22改为:
I:=3
if X<Y goto B3
考虑B2_22中I:=3
外提的问题。
通过计算必经结点集可以发现,B2_22确实是循环出口结点B4_44的必经结点,那么我们是否可以将I:=3
进行代码外提呢?同样的论述过程,我们姑且认为可以,那么如果程序的执行过程是2-3-4-2-4-5,则执行B5_55时的变量I
值为2,从而J的值也是2;而如果不进行外提操作,那么同样的执行过程下,I
和J的值都应该是3,而非2.这一错误的原因在于,循环中除了B2_22以外,B3_33也对同一个变量进行了定值。
所以从这个例子中我们可以看到,当我们将循环不变运算A:=B op C外提时,要求循环中的其它地方不再有A的定值点。
第三个例子,也是最后一个例子,我们看下面一个程序流图:
考察基本块B4_44中循环不变运算I:=2
这一语句的外提操作。首先,B4_44结点就是整个循环的出口结点,并且B4_44结点也是其本身的必经结点;其次整个循环{B2_22,B3_33,B4_44}中也没有第二个对于变量I
定值的语句。那么我们是否可以将这一语句外提呢?同样的方法,我们还是暂且认为这一语句可以外提,再比较对于同一个执行过程,外提前后的执行结果。我们考虑X=0、Y=2的情况,循环的执行流程是2-3-4-2-4-5,代码外提前,程序的执行结果为J=2;而代码外提后,程序的执行结果为J=3.
通过这个例子,我们看到,当将循环不变语句A:=B op C进行代码外提时,要求循环中所有对于A的引用点都是并且仅仅是这一定值语句所能到达的。上述的例子中,能够到达A:=I+1
这一对于I
的引用的定值语句,不仅有B4_44中的I:=2
,还有B1_11中的I:=1
.
最后我们给出查找循环L中不变运算的算法:
以及代码外提算法:
强度削弱.
我们要介绍的第二种循环优化技术叫做强度削弱。强度削弱是将程序中执行时间较长的运算替换为执行时间较短的运算。例如,最常见的就是将循环中的乘法运算用递归的加法运算来代替。我们考察前面经过了代码外提的示例流图:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
不难看出{B2_22,B5_55}是一个循环,并且B2_22是循环的入口结点。我们注意序号为13的语句,这里的变量I
是一个递归赋值的变量,每循环一次,它增加一个常量1。另外,序号为4和8的语句在计算T2_22和T6_66时,都会使用I
的值,并且T2_22和T6_66都是I
的线性函数,每循环一次,它们都增加一个常量10.因此,如果把4和8外提到循环的前置结点中,并且在13号语句之后添加上为T2_22和T6_66增加常量10的语句,程序的运行结果依然不变。
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
经过上述变换,循环中原来的乘法运算4和8被替换为了在前置结点中进行一次初始化的乘法运算(计算初值)以及在循环中递归赋值的加法运算(4’)和(8’).不仅加法运算一般来说比乘法运算快,而且这种在循环前计算初值,再于循环末尾进行常量递增的运算,可以利用变址器提高运算速度,从而使运算的强度得到削弱。所以我们称这种优化技术为强度削弱。
强度削弱不仅对于乘法可以实行,对于加法也可以实行。例如上图中序号为5和9的语句,T3_33与T7_77的计算中引用到了T2_22和T6_66,并且T2_22和T6_66也是递归赋值的变量,每循环一次,它们的值就增加一个常量10.而T3_33与T7_77的另一个运算对象都是循环不变量,所以每循环一次,T3_33与T7_77的值就增加一个常量10.很自然地,我们会想到对它们进行强度削弱,即外提一个初始化语句,在循环的末尾添加常量递增语句,得到的优化结果如下所示:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
从上面的例子中我们可以看到:
- 如果循环中有A的递归赋值A:=A±b1_11,并且循环中另一个变量B的赋值运算可以写为B:=k*A±b2_22的形式,那么对于B的赋值运算,我们可以进行强度削弱;
- 进行强度削弱之后,循环中可能会出现一些新的无用赋值,例如上图中的(4’)和(8’),因为循环中不再使用T2_22和T6_66,那么如果循环出口之后它们也不是活跃变量,是完全可以删除的;
- 循环中下标变量的地址计算是相当耗费时间的,这里介绍的方法对削弱下标变量地址计算的强度是非常有效的。前面的例子中,数组是二维的,如果我们考察一个更高维度的数组,将会进一步看到强度削弱的作用。对于下标变量地址计算来说,强度削弱实际上就是实现下标变量地址的递归计算。
删除归纳变量.
我们要介绍的最后一种循环赋值技术叫做删除归纳变量。讲述具体的流程之前,首先给出基本归纳变量以及归纳变量的定义:
【定义】基本归纳变量:循环中对于变量A的赋值只有唯一的、形如A:=A±b1_11的赋值,那么称A为循环中的基本归纳变量;
【定义】归纳变量:如果A是一个基本归纳变量,循环中对于B的赋值总是可以化归为A的同一个线性函数,即B:=k*A±b2_22,那么我们称B是一个归纳变量。
考察进行了代码外提之后的示例程序流图:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
不难发现变量I
是循环{B2_22,B3_33}的基本归纳变量,T2_22和T6_66是循环中与I
同族的归纳变量,再继续发掘,T3_33和T7_77也是与I
同族的归纳变量。
一个基本归纳变量除了用于自身的递归赋值以外,往往只在循环中用于计算其他的归纳变量和控制循环的进行。经过了强度削弱之后的示例程序流图如下所示:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
关注基本块B3_33可以发现变量I
除了用于自身的递归赋值以外,只用于控制循环的进行。这时,我们可以使用与I
同族的某一个归纳变量来代替I
控制循环的进行,从而删除变量I
.例如,我们选取T3_33(T7_77与T3_33都在循环中被引用,所以是一样的,而T2_22和T6_66在循环中没有被引用)来代替I
, 由于T3_33可以写为10*I+T1
,并且循环的控制条件为I>10
,所以用T3_33代替了I
之后,控制条件变为了T3>100+T1
.为了不在每一次进行判断时都计算100+T1_11的值,我们引入新的临时变量R:=100+T1_11,所以序号为2的if语句改写为:
R:=100+T1
if T3>R goto (15)
然后我们就可以删去变量I
了,这一优化技术称为删除归纳变量,也叫变换循环控制条件。如果我们假定T2_22和T6_66在循环出口之后也不是活跃的,那么我们完全可以删去这两个临时变量(因为它们在循环中也没有被使用)。但如果我们在选择代替I
的变量时选择了T2_22或T6_66,那么(4’)或(8’)就不能删除了,最后完成删除归纳变量的中间代码如下:
【下图引用自中南大学徐德智老师的编译原理2020年授课PPT】
删除归纳变量是在强度削弱之后进行的。我们在下面统一给出强度削弱和删除归纳变量的算法:
【编译原理】中间代码优化(三) 循环优化相关推荐
- 编译原理实验(三)——LR(0)语法分析
编译原理实验(三)--LR(0)语法分析 实验要求 参考程序 实验结果 程序输入说明 截图 实验要求 根据LR(0)分析法编写一个语法分析程序 直接输入根据已知文法构造的分析表M;对于输入的文法和符号 ...
- 【编译原理】代码优化,流图/DAG优化
优化原因 逐条语句进行的代码生成策略经常产生含有大量冗余指令和次最优解结构的目标代码. 代码优化就是被优化程序进行一种语义保持的变换 优化位置 中间代码优化(与机器无关) 目标代码优化(与机器有关) ...
- 编译原理实验(三)词法语法分析综合设计
版权声明:本文为原创文章,版权归 Geekerstar 所有. 本文链接:http://www.geekerstar.com/technology/105.html 除了有特殊标注文章外欢迎转载,但请 ...
- 《编译原理》(三)词法分析
hello大家好,今天我们来学习词法分析.教妹学编译原理,没见过这么酷炫的标题吧?"语不惊人死不休",没错,标题就是这么酷炫. 我的妹妹小埋18岁,校园中女神一般的存在,成绩优异体 ...
- 【编译原理】 实验三 LL(1)分析法(LL1分析表的自动生成)
写在前面 由于代码较长,csdn对文章总长度有字数限制,想只看完整代码的请移步另一篇博客. https://blog.csdn.net/qq_46640863/article/details/1257 ...
- 编译原理实验报告三:语法分析(PL0,词法分析,语法分析,中间代码生成)
实验报告三:语法分析 一.实验目的 通过设计.开发一个S语言的语法分析程序,实现对源程序的语法检查和结构分析,加深对相关课堂教学内容的理解,提高语法分析方法的实践能力. 二.实验要求 根 ...
- 《编译原理》第三章知识点
- 【编译原理】第三章作业
151220129 计科 吴政亿 厚书 3.3.2 (3) 由a与b组成的,后缀为aaa,aab,aba,abb的串 厚书 3.3.5 定义β为所有非元音小写字母的集合,则其正则表达式为β∗a(a│β ...
- [编译原理]DO-WHILE循环语句的翻译程序设计(LR(1)方法、输出四元式)C++实现
题目: DO-WHILE循环语句的翻译程序设计(LR(1)方法.输出四元式) 1 课设任务概述 初始条件: 理论:完成编译原理,数据结构.高级编程语言.汇编语言等相关课程的学习,基于计算机专业知识 ...
最新文章
- 人脑为何能够快速学习新知识? 用AI的视角来分析
- 漫画:前端发展史的江湖恩怨情仇
- 服务器托管租用商考虑什么
- Chromium on Android: 认识Chromium WebView
- SAP特性,物料特性,批次特性建立的BAPI函數
- 共建智慧海关 第四范式打造快、准、稳海关单证识别系统
- 头条的_signature这个如何_今日头条详细页面的cookie加密
- 关于Android的硬件抽象层添加进Android系统默认编译的问题
- 上海音乐学院计算机考研难吗,上海音乐学院考研难吗?一般要什么水平才可以进入?...
- node 升级_那些修改node_modules的骚操作
- agp计算机组装什么意思,教你怎么组装电脑
- Ubuntu18.04安装Pangolin0.6
- 均匀分布、正态分布、二项分布、泊松分布、指数分布
- python视频搬运_拒绝降权!教你用 Python 确保制作的短视频独一无二
- 全景分割相关论文写作与准备笔记
- 控件为何不能自动装载?--全面总结
- 微信投票服务器在哪做,微信的投票功能在哪里?怎么制作微信投票活动?
- 利用html加css以及JavaScript写一个学生后台管理系统简单平台
- CSS、JavaScript学习过程
- Win11的两个实用技巧系列之关闭分屏模式方法
热门文章
- Exp-00079解决方法
- 自由与平等在某种意义上是相互矛盾的
- 泰克示波器采样率问题解答
- 一级计算机第十一套ppt讲解,11个PPT技巧,掌握了,提升PPT逼格不是一点点,瞬间高大上许多!...
- uni-app 简单引入iconfont示例(阿里妈妈)
- 2012年北邮网研上机
- 个人练习-Leetcode-1942. The Number of the Smallest Unoccupied Chair
- SpringBoot2.x系列教程(三十七)SpringBoot web之网站图标(Favicon)配置
- 1-1 【理论】01-文件上传/下载漏洞的业务场
- FPGA学习步骤,我的体会