编译程序生成机器码的好处/缺点与仅从源代码构建AST并在遍历树时执行操作相比有何优劣之处?
在一种方式中选择而不是另一种方式,是否有特定的原因?
编译程序生成机器码的好处/缺点与仅从源代码构建AST并在遍历树时执行操作相比有何优劣之处?
在一种方式中选择而不是另一种方式,是否有特定的原因?
解释AST的速度通常比运行执行相同任务的机器码要慢得多,一般会慢20倍左右。
其中的优点是AST生成速度更快,因此需要比大多数编译器生成代码的时间要短。由于整个代码生成阶段可以被忽略,所以AST解释器也往往比编译器简单。
因此,如果您有一个不进行重型计算的程序,使用解释器将启动更快。另一方面,如果您有一个程序在一个稀缺的循环环境中经常或持续运行,则最好使用编译器。
一些编程环境(例如许多LISP)包括解释器用于开发代码,因为它支持快速的调试周期和编译器用于生成速度较快的代码的生产。其中一些系统允许自由混合解释和编译代码,这本身就是非常有趣的。
编译成字节码是一个折中方案:编译速度比机器码更快,但执行速度比AST快。尽管如此,现代字节码解释器通常会在程序运行时“即时”将其编译为本机代码。例如,Sun的HotSpot JVM的名称源于此,它将Java字节码中的“热点”编译为本机代码以在运行时加速程序。
回答评论中的问题
上文提到的20倍是一个参考数字,因为很少有现代语言系统使用纯AST解释器(重要例外是命令shell,但它们大多是很久以前开发的,速度基准测试不常见)。他们太慢了。我的背景是lisp解释器。我实现过一些。例如,这里有一组Scheme基准测试 。易于选择与AST解释器相对应的列。如果需要,我可以从ACM Digital Library archive 中发布更多类似的内容。
另一个粗略的基准测试:Perl使用高度优化的AST解释器。在我的机器上紧密循环中添加10百万个浮点数需要大约7秒钟。编译后的C代码(gcc -O1)需要大约1/20秒。解释平坦的“字节码”比树形结构要容易得多。有许多公认的优化技术(例如线程代码)可用于字节码解释器,而AST遍历解释器注定会非常缓慢。
另外,除非你必须进行一些重量级的优化(如死代码消除、常量折叠、寄存器分配、有效指令调度),编译非常简单,可以分成显然容易的小步骤。另一方面,任何非平凡语言的简单解释总是复杂的,不能被简单和明显地分解。
w+x+y+z
->load, add, add, add
,但(load (add (add (add))))
->load node、load value、add、load node、load value、add、load node、load value、add、load node、load value、add
,可能会有许多缓存未命中。我有一种感觉,这将比20倍的时间更长。另一方面,x=y
->load x、store y
,但(store (load))
->load node、load value、load node、store value
,远少于20倍(或者由于缓存未命中,再次增加)。 - Sebastian Mach