我目前正在编写compiler,我似乎遇到了一些问题,导致它输出的代码在合适的时间范围内执行。
编译器的简要概述:
7Basic是一个编译器,旨在将7Basic代码直接编译为目标架构/平台的机器代码。目前,7Basic在给定源文件的情况下生成x86程序集。
问题是编译器生成的汇编代码缓慢且效率低。
例如,this代码(编译为this汇编代码)的执行时间比the equivalent C code长近80.47倍。
部分问题是编译器正在生成如下代码:
push eax
push 5000000
pop ebx
pop eax
而不是更合乎逻辑:
mov ebx,5000000
......完成同样的事情。
我的问题是:有哪些技巧可以避免这类问题?解析器基本上使用递归来解析表达式,因此生成的代码反映了这一点。
答案 0 :(得分:15)
一种技术称为peephole optimisation。这需要一种迭代方法来清理汇编代码。基本上,您扫描汇编代码,一次只查看两个或三个指令,并查看是否可以将它们简化为更简单的操作。例如,
push eax ; 1
push 5000000 ; 2
pop ebx ; 3
pop eax ; 4
第一步将查看第2行和第3行,并将其替换为:
push eax ; 1
mov ebx,5000000 ; 2a
pop eax ; 4
其次,您可以考虑1和4,如果中间指令未触及eax
,请将它们全部删除,留下您想要的内容:
mov ebx,5000000 ; 2a
答案 1 :(得分:6)
您可能需要考虑生成C代码而不是汇编,然后让C编译器(例如gcc)为您处理代码生成。尝试重新发明轮子毫无意义。
答案 2 :(得分:4)
我正在学习编译课程。我在输出高效代码方面取得了一些很大的进步,但是你应该看看龙书。这是一个成年礼。你应该看看Jeremy Bennett的书中的代码,编译技术简介:使用ANSI C,LEX和YACC的第一门课程。这本书很难找到,但你可以免费下载编译器的源代码
http://www.jeremybennett.com/publications/download.html
代码生成器文件(cg.c)具有一些用于生成相当优化的代码的函数。目标语言不是i386,但您应该考虑如何描述寄存器并跟踪符号表条目的存储位置。他的输出组件可以进一步优化,但它提供了一个很好的基础,可以生成可以在某些方面与gcc -S的输出相媲美的代码。
一个通用优化是在进入函数时减去堆栈指针以保留所有本地和临时变量的空间。然后只需引用偏移而不是不断地推/弹。
例如,如果您的中间代码是四元组列表,则应简单地为每个函数迭代它并跟踪最大偏移量。然后输出该行以减去堆栈上的空间量。这消除了打开和关闭这么多变量的需要。要删除弹出它们的需要,您只需将它们的值从堆栈中的偏移量移动到寄存器中即可。这将显着提高性能。
答案 3 :(得分:2)
特定代码生成器可能会发出您列出的指令序列的原因有很多。最有可能的是,你正在使用的代码生成器并没有非常努力地发出最佳代码。
这种发出的代码模式告诉我你的代码生成器不知道x86有“mov immediate”指令直接将常量值嵌入到指令流中。具有立即值的操作码的x86编码可能会有点复杂(可变长度R / M字节),但如果您想使用许多x86指令,则已经需要这样做。
此发出的代码还表明代码生成器不知道EBX指令未修改EAX。这感觉就像codegen是模板驱动而不是离散逻辑。
当编译器的内部中间操作表示不够详细以表示目标体系结构的所有方面时,就会发生这种代码。如果代码生成器体系结构最初是为RISC指令集设计的,但已被重新用于发出x86指令,则尤其如此。 RISC架构往往只有很少且非常简单的加载,存储和操作reg / reg指令,而x86指令集已经有机地发展了几十年,包括各种直接在内存上运行的操作码,指令中的内联常量,还有其他一些东西。如果编译器的中间表示(表达式图形)是为RISC连接的,那么很难让它能够解决x86的各种各样的细微差别。
答案 4 :(得分:2)
窥孔优化会有所帮助,但一个明显的问题是你的编译器没有进行寄存器分配!
http://en.wikipedia.org/wiki/Register_allocation
如果你想获得严重的性能水平,那么你必须要考虑到这一点。如果你“在飞行中”贪婪地做它,它可以在一次通过中完成。