将程序编译为机器代码有什么好处/缺点,而不是简单地从源代码构造AST并在遍历树时执行操作?
你有什么理由想要一个人做另一个吗?
答案 0 :(得分:9)
解释AST通常比运行相同的机器代码要慢得多。通常为20倍。
优点是AST生成速度更快,因此比大多数编译器生成代码所花费的时间更少。 AST解释器也比编译器更简单,因为可以忽略整个代码生成阶段。
因此,如果您的程序没有进行繁重的计算,那么使用解释程序的程序就会更快。另一方面,如果你的代码在循环稀缺的环境中经常或连续运行,那么最好不要编译。
某些编程环境(例如许多lisps)包括用于开发代码的解释器,因为它支持快速调试周期,以及用于在开发完成时生成快速代码的编译器。其中一些系统允许自由混合解释和编译代码,这本身就很有趣。
编译为字节码是一个中间立场:编译比机器代码更快,但执行速度比AST快。尽管如此,现代字节码解释器通常在程序运行时“及时”编译为本机代码。这例如是Sun的HotSpot JVM的名称的来源。它将Java字节码中的“热点”编译为本机代码,以便在运行时加速程序。
回复评论中的问题
上面提到的因子20有一个问题。支持这个数字的引用很老,因为很少有现代语言系统使用纯AST解释器。 (一个值得注意的例外是命令外壳,但其中大部分是很久以前开发的,速度基准测试并不常见。)它们太慢了。我的上下文是lisp解释器。我实施了一对。 Here for example is one set of Scheme benchmarks。与AST解释器对应的列很容易挑选出来。如果有需求,我可以从ACM数字图书馆档案中发布更多和类似的内容。
另一个粗略的基准:Perl使用经过大量优化的AST解释器。要在我的机器上紧密循环添加1000万个浮动需要大约7秒钟。编译C(gcc -O1)大约需要1/20秒。
评论者提出了4个变量的加法作为例子。分析忘记了查找的成本。解释器和编译器之间的一个明确的分界线是符号的预先计算的地址或帧偏移。在一个“纯粹的”翻译中没有。因此,添加4个数字需要在运行时环境中进行4次查找,通常是哈希表 - 至少100条指令。在良好的编译代码中,在x86上添加4个整数需要2个指令,另外一个用于存储结果。
“纯”AST插件和编译机器代码之间有许多阴影。根据语言,可以将符号偏移编译到AST中。这有时被称为“快速链接”。该技术通常可将速度提高一倍或更多。然后是“compile-to-bytecode and go”系统,如Python,PHP,Perl,Ruby 1.9+。它们的字节码实际上是线程代码(操作码会导致非常复杂的事情发生),因此它们比机器代码更接近AST。然后是我上面提到的JIT字节码解释器。
关键是20个纯AST解释器的因子是一个书挡而机器码是另一个。在中间有许多变体,每个变体都有优点和缺点。
答案 1 :(得分:3)
尚未提及的编译的另一个优点是它通常比直接的临时解释容易得多。通常,未经处理的源语言不太适合直接解释,而将其简化为更简单的语言将有助于更有效和直接的解释。
例如,语言可能具有词法范围,每次取消引用变量或函数参数时都需要进行名称查找。但是,一个简单的转换过程将枚举变量并插入隐式存储管理结构,这将使解释变得更加简单和高效 - 数组访问比带有文本键的哈希表快得多。另一个这样的例子是闭包处理 - 一个lambda提升通道比任何可能的临时方法都简单得多。
解释平面“字节码”比树更容易。对于字节码解释器有许多众所周知的优化技术(例如,threaded code),而AST行走解释器注定要死得很慢。
并且,除非你必须进行一些重量级的优化(例如死代码消除,常量折叠,寄存器分配,高效的指令调度),否则编译非常简单并且can be split into ridiculously obvious small steps。另一方面,对任何非平凡语言的直接解释总是很复杂,不能分成任何简单明了的东西。