优化(Optimization)是现代编译技术中一个非常重要的话题。仅就研究现状而言,随着其他阶段的理论研究与实现技术的逐步完善,有理由相信优化技术将是评价未来编译器优劣的唯一标准。
经过先前章节的学习,读者应该知道IR生成及目标代码生成都是一种机械的翻译过程,它们更多考虑的是如何实现等价地转换,而往往忽略了目标代码的品质因素。随着编译器的广泛应用,人们似乎已经不能接受这种平凡的翻译过程了。无论何时何地,目标程序的执行效率仍然是程序员关注的重要指标。由此,编译器设计者引入了编译优化的概念,试图通过一些特殊的算法使得编译器产生更优的目标代码。这里的“更优”主要体现在以下三个方面:代码执行的效率、代码执行的所需空间大小、代码本身的大小。一般而言,以上三者之间并不存在矛盾。但有时却并非如此,在一些特殊情况下,即使是程序员可能也无法准确评估并作出最佳抉择,更何况是编译器。举个简单的例子,通常认为指令行数越少(即目标程序所占用空间越小),则执行效率就必定越高(不考虑循环跳转)。不过,对于某些目标机而言,不同指令所需的机器周期可能不同。在这种情况下,代码执行效率及其本身所占用空间之间的关系可能是非常微妙的,甚至会出现一种矛盾的状态。实际上,在繁多的体系结构中,这种情况并不罕见。因此,试图从理论上分析论证优化算法的成效可能是比较复杂的。不过,当明确了应用目标后,这个问题就会变得相对容易了。例如,针对当今的x86目标机来说,代码执行效率可能是程序员更关心的因素,为此他们往往愿意以牺牲一定的存储空间作为代价。在这种情形下,代码执行效率就是评价算法的最主要因素。然而,对于一些嵌入式系统的编译器而言,情况却恰恰相反。此类目标机的存储空间有限,而用户却并不太关注程序的执行时间,因为嵌入式系统的程序一般是后台运行的。因此,耗用存储空间多少将是评价此类编译器优化算法的主要因素。
经过计算机科学家的不懈努力,无论是理论研究还是实践应用,优化技术都取得了较大的进展。即便如此,仍然不可能达到“最优”的目标,编译器能做到的仅仅是改善代码的性能而己。因此,从来没有一本编译原理的书将“Optimization”译成“最优”。当然,人们也很难使用一个量化的标准来衡量优化成果。对于不同的输入,优化的成果是不可判定的。在一些特殊的代码情形下,某些优化算法甚至可能降低目标程序的性能。
寄希望于通过优化算法尽可能地改善目标代码的思想是完全可以接受的,但有一条底线是不能突破的,那就是代码的正确性。以牺牲正确性为代价讨论优化算法是没有任何意义的。即使错误仅仅是“理论上”存在,也绝不能忽视。任何优化的前提都是必须保证不会将一个正确的程序转换成不正确的程序。这是优化中一个非常重要的原则——保守安全原则。实际上,为了达到这一目的,编译器设计者不得不面临许多复杂的问题,例如,数据流分析、控制流分析、别名分析、依赖分析等。这些分析算法本身并不会改善代码的性能,却为许多优化算法提供了非常重要的信息资源。在某些情况下,分析算法的完善可能会对优化算法产生极其深远的影响。由于两者之间的关系非常密切,在现代编译技术中,经常将两者都作为优化技术讨论的话题。
下面,通过一个Neo Pascal的实例分析,让读者对优化有一个感性的认识。如表7-1所示,仔细观察B、C两列的IR,不难发现,C列的代码品质、执行性能较B列有显著的改善。就本例而言,已经得到了一个理论最优解,由此可见,优化的意义是毋庸置疑的。
表7-1 IR优化实例分析(www.daowen.com)
最后,简单讨论一下关于优化可行性的话题。读者应该知道,任何算法的执行都需要付出时间及空间的代价。人们通常能够容忍在编译阶段耗费时空资源来改善代码的品质,最终达到相对最优的运行效果。不过,这并不是绝对的。编译器设计者通常需要考虑以下两个问题:
第一,如果优化所付出的代价无法或很难用运行时的收益来弥补,那么,该优化算法的可行性是值得商榷的。
第二,根据硬件环境及编译模型进行可行性分析。对于有些编译模型而言,花费大量的时间或空间进行优化可能是无法接受的。尤其是在设计动态编译器时,算法的时空耗费可能是决定算法可行性的最关键因素。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。