优化编译器就是要消除简单语言翻译中可能引入的低效率,改进目标程序的性能。一个
编译器的好坏主要就是看这个编译器
优化的效果是否良好。
编译器的研究综合了计算机科学中的操作系统、计算机系统结构、图算法、人工智能等众多领域,因此对编译器的研究要求研究者在各方面都有很深的理解。编译器的研究可以追溯到卜世纪50年代。从Fortran语言出现的那天起,研究者们就在不断地探索怎样使高级语言编译后能够和机器语言编’写的程序具有相当的效率。Fortran语言的成功很大程度卜得益于它从一开始就有很好的编译器。随着越来越多的高级语言的出现,计算机的应用领域越来越广泛,编译器所扮演的角色显得越来越重要。
编译器的结构包括
词法分析器(Lexicai Analyzer),语法分析器(Syntax Parser),语言分析器(Semantic Analyzer),中间代码生成器(Intermediate Code Generator),代码优化器(CodeOptimizer),符号表(Symbot Table),错误处理器(Error Handler)。词法分析器直到中问
代码生成器又称为前端(FrontEnd),代码优化器到代码生成器又称后端(Back End)。这个界限并不是严格的,而且有些研究者喜欢把优化过程独屯提出来讨论。这样的分层是很有工程价值的,在大型的多语言的编译系统中,任何语言的编译器都共用一个后端,因为后端与高级语言之间几乎没有联系,大多与机器相关;如果有开发者想在某编译系统中添加一种语言的编译功能,只需编写该语言的前端即可;如果需要将已有的编译器移植到新机器上,只需重新编写一个与机器相关的后端即町,前端可以不加修改或者稍作修改后重复使用。以前要编在m种机器上运行,能编译n种语言的编译系统,需要编写n·m个不同的编译器;而按照这种一r程分层,则只需编写n个前端以及m个后端即可。著名的编译系统GCC(GNU Compiler Collection)就是按照这种工程分层方式开发的。
编译器的优化步骤在整个编译器中是最重要的,也是最难的。它重要是因为一个编译器的好坏主要就是看这个编译器优化的效果是否良好。如果一个编译器的优化效果很差,那么由该编译器编译出与用机器语言编写的程序对系统资源的开销是相当大的,而程序设计语言的设计者往往希握编译器能够编译出与用机器语言编写的程序效率相当的程序;它难是因为优化中的众多问题都没有定型的好算法。有些优化问题的求解甚至是不可计算的。现代系统结构中流水线,超标量以及多核处理器的出现无疑给编译器的设计实现者加重r任务。
优化的正确性原则是优化前后的代码对于任何输入(合法或者非法),都应给出相同的结果。这条原则是显然的,但并不是总那么容易做到。曾经有一段时间,GCC在Intel的机器上对浮点数的存取优化就没能做到这一点。优化代码的提供者没有考虑到Intel的FPU是扩展的80位的,因此对于float,double类型在寄存器中的数据和存在内存中的数据是不一样的,即使逻辑上相等的数据拿寄存器中的与内存中的比较也会得到不相等的结果,优化者期望通过将l临时变量存在寄存器中以获取效率,但导致了与未优化的代码产生不同输出的结果。
普通优化一般都会经过以下几个过程:常量转换,将源文件中的常量变量及常量表达式都用其真实值来代替,这将可以节省一定的时间和空间;公共子表达式求值,将多次出现的子表达式预先求值,并存入临时变量,这样可以避免重复求值,但必须保证子表达式的值在任何地方都不会改变;冗余代码的删除,将那些并不会用到的代码删除;优化存储器.将频繁使用的临时变量放入寄存器中;表达式求值优化,改变表达式求值顺序,有时矗T能达到优化目的;函数过程在线展开,将自程序代码内嵌到调用代码中,这样可以避免函数调用的开销。普通优化还有很多,此处只是试举几例。针对流水线的优化尤其需要注意代码的顺序以避免各种流水线冒险:结构冒险,当硬件知指令重叠执行中不能支持指令所有可能的组合时发生的资源冒险;数据冒险.在同时执行的几条指令中,一条指令依赖于前一条指令的数据却得不到时发生的冒险;控制冒险,流水线中的转移指令或其他改写
程序计数器的指令造成的冒险。现在的指令集系统和CPU设计一般不会出现结构冒险了。
数据冒险一般出在算术指令中,这是编译器最好解决的一种冒险。出现这种冒险,最显然的处理办法是加上一条nop;但是这种处理方式既浪费了时间,又浪费了能量,如果编译器能有效地调整指令顺序,是有可能避免这两种冒险的。如在MIPs的五段流水线中:
在此出现了数据冒险,如果没有发生中断,sub访问r1时add还没有及时更新rl中的内容,因此sub会访问到错误的数据。但如果编译器将后面的一些不会干扰到这块代码、也不依赖于这个代码的指令加到这两条指令之间,就可以避免数据冒险了。
优化编译器的设计是一个又深又广的话题,它不仅要应用计算机系统结构、人工智能等领域的前沿结果,还要求设计在软件工程卜给予足够的考虑。尤其在现今还未能解决的诸多优化问题中,编译器设计还需要和众多学科共同发展,以求找到可行高效的解决方案。