【问题标题】:What is the difference between compilation and interpretation?编译和解释有什么区别?
【发布时间】:2014-08-10 00:12:42
【问题描述】:

我刚刚和一位同事进行了一次谈话,在哪里谈论 V8 JavaScript 引擎。根据维基百科,

V8 在执行 JavaScript 之前将其编译为本机机器码 [...],而不是更传统的技术,例如解释字节码或将整个程序编译为机器码并从文件系统执行。

哪里(如果我错了,请纠正我)“解释字节码”是 Java 的工作方式,“编译整个程序”适用于 C 等语言或 C++。现在我们想知道、辩论和提出关于差异、相似性的错误断言和假设。为此,我建议咨询 SO 方面的专家。

那么,谁能做到

  1. 命名、解释和/或引用所有主要方法(例如预编译与运行时解释)
  2. 可视化或提供有关源、编译和解释之间关系的方案
  3. 给出#1的主要方法的例子(命名编程语言)。

注意事项:

  • 我不是在寻找关于不同范式的冗长平淡无奇的文章,而是在视觉上提供支持的快速概述。
  • 我知道 Stackoverflow 并非旨在成为程序员的百科全书(而是针对更具体问题的问答平台)。但由于我能找到很多热门问题,那种为某些主题提供了百科全书式的观点(例如[1][2][3][4][5]),我开始了这个问题。
  • 如果此问题更适合任何其他 StackExchange 网站(例如 cstheory),请告诉我或标记此问题以供审核。

【问题讨论】:

  • 很惊讶这没有更多的选票,因为这是一个重要的问题,有一些很棒的答案。

标签: compiler-construction compilation interpreter interpretation


【解决方案1】:

几乎不可能回答您的问题,原因很简单:没有几种方法,它们是一个连续统一体。这个连续体中涉及的实际代码也相当相同,唯一的区别是事情发生的时间,以及是否以某种方式保存了中间步骤。这个连续体中的各个点(不是一条线,一个级数,而是更多的一个具有不同角的矩形,您可以靠近)是:

  1. 阅读源码代码
  2. 理解代码
  3. 执行你所理解的
  4. 沿途缓存各种中间数据,甚至永久保存到磁盘。

例如,纯粹的解释编程语言几乎不会做#4,而#2 有点隐式发生在 1 和 3 之间,所以你几乎不会注意到它。它只是读取代码的一部分,并立即对它们做出反应。这意味着实际开始执行的开销很低,但是例如在一个循环中,相同的文本行被读取并重新读取。

在矩形的另一个角落,有传统的编译语言,通常,第 4 项包括将实际机器代码永久保存到文件中,然后可以在以后运行。这意味着您在开始时要等待相对较长的时间,直到整个程序被翻译(即使您只在其中调用一个函数),但 OTOH 循环更快,因为不需要再次读取源代码。

还有一些介于两者之间的东西,例如虚拟机:为了可移植性,许多编程语言不会编译成实际的机器码,而是编译成字节码。然后有一个编译器生成字节码,还有一个解释器获取这个字节码并实际运行它(有效地“将其转换为机器码”)。虽然这通常比编译和直接转为机器代码要慢,但将这种语言移植到另一个平台更容易,因为您只需移植通常用高级语言编写的字节码解释器,这意味着您可以使用现有的编译器来执行这种“有效地转换为机器代码”,并且不必为您要运行的每个平台制作和维护后端。此外,如果您可以将编译为字节码一次,然后只分发编译后的字节码,那么这个 可以 更快,这样其他人就不必花费 CPU 周期在例如在您的代码上运行优化器,并且只为字节码到本机的翻译付费,这在您的用例中可能可以忽略不计。此外,您没有分发源代码。

介于两者之间的另一件事是即时编译器 (JIT),它实际上是一个解释器,它以编译的形式保存它曾经运行过的代码。这种“保留”使其比纯解释器慢(例如,增加开销和 RAM 使用导致交换和磁盘访问),但在重复执行一段代码时使其更快。它也可以比代码的纯编译器更快,例如只有一个函数被重复调用,因为如果不使用它不会浪费时间编译程序的其余部分。

最后,您可以在这个矩形上找到其他点,例如通过不永久保存已编译的代码,而是再次从缓存中清除已编译的代码。这样你可以例如在嵌入式系统上节省磁盘空间或 RAM,代价是可能不得不再次编译一段很少使用的代码。许多 JIT 编译器都这样做。

【讨论】:

  • “这种‘保留’使它比纯解释器慢”——相反:JIT 通常(并且最初设计是)比纯解释器快得多.
  • @konrad-rudolph 请参阅下面的说明。对于翻译整个程序并运行每个表达式一次,它会比较慢(绝对),但因为这在实践中几乎不会发生,因为至少某些部分通常执行两次或根本不执行, JIT 更快。但是对于 JIT 比纯解释器慢的示例,请看“Hello World”示例。
  • 我不相信。同样的道理,编译代码比解释代码慢,但这种说法显然是误导性的,除了一些琐碎的情况(你的“Hello World”示例)之外,它根本不正确。在这两种情况下,“比纯解释器慢”都不是信息丰富的陈述。
  • @konrad-rudolph 我在这里解释不同方法背后的理论和权衡,这是选择任何混合方法的权重所需要的(例如 JIT,它们是混合编译器/interpreter/cache 结构)。 正是您需要在这里了解的非实际的理论案例。只有然后您才会查看您的具体实际用例(对于网站弹出窗口、Web 输入字段过滤器、一次性批处理操作或系统编程来说不同)并决定哪种组合是最好的给你。
  • @konrad-rudolph 我在“比纯解释器慢”后面添加了一个限定词,以举例说明哪个部分实际上更慢。此外,查看 Mac 上的 Safari 以了解使用几乎所有方法的示例。主机本身是编译的,执行速度最快。 JavaScript 通常被解释为一次性的,如 onLoad(开销最小),字节码被解释为运行几次(更多开销但没有重复解析),重复运行的东西(如输入字段过滤器)使用编译新的 FTL 功能。
【解决方案2】:

现在许多执行环境都使用字节码(或类似的东西)作为代码的中间表示。因此,源代码首先编译成中间语言,然后由虚拟机解释(解码字节码指令集)或进一步编译 转化为机器码,由硬件执行。

很少有生产语言被解释而不被预编译成某种中间形式。但是,很容易将这样的解释器概念化:只需考虑一个类层次结构,其中包含每种语言元素类型的子类(if 语句、for 等),并且每个类都有一个 Evaluate 方法来评估给定节点。这也通常称为interpreter design pattern

例如,考虑以下代码片段在假设的解释器(用 C# 实现)中实现 if 语句:

class IfStatement : AstNode {
    private readonly AstNode condition, truePart, falsePart;

    public IfStatement(AstNode condition, AstNode truePart, AstNode falsePart) {
        this.condition = condition;
        this.truePart = truePart;
        this.falsePart = falsePart;
    }

    public override Value Evaluate(EvaluationContext context) {
        bool yes = condition.Evaluate(context).IsTrue();
        if (yes)
            truePart.Evaluate(context);
        else
            falsePart.Evaluate(context);
        return Value.None; // `if` statements have no value.
    }
}

这是一个非常简单但功能齐全的解释器。

【讨论】:

  • “真正的口译员”是一个不幸的词选择。这是层次和 POV 的问题。可以说,即使是字节码解释器也不是“真正的解释器”,因为它需要有人在解释源代码之前将其编译为字节码。 “真正的解释器”会逐行或逐语句读取源代码,并在识别出每一个后立即执行。字节码解释器也是一个解释器,但前提是你忽略了前面的编译步骤,这会将它变成一个 VM 或虚拟 CPU。
  • @uliwitness 我不同意这个评估。这里有两个不同的阶段不应该混为一谈:Python 解释器 一个真正的解释器,即使它解释的是字节码而不是 Python 代码——要么逐个语句(即所谓的 REPL)要么一次一个文件。原始源代码被编译成中间格式,但这并不影响中间格式随后被解释的事实,并且这一步的实现被归类为解释器。跨度>
  • 但是你在哪里停止这种区别?如今,英特尔 CPU 包含一个解释器,可将英特尔指令集翻译成实际的类似 RISC 的硬件指令。在那个 POV,所有代码都被解释。我们需要在这里区分术语。传统上,“解释性编程语言”意味着源代码是解释性的,而不是字节码。既有称为“解释器”的算法(字节码解释器使用),也有 OP 询问的“解释型编程语言”的高级概念。我们不应该把它们混为一谈。
  • Konrad:按照这个推理(经典的 pre JIT)Java 也是一个解释器,因为它编译为字节码,而 VM 是一个解释器。用一个阶段来命名整个过程是很危险的。编译的一个好的工作定义是翻译到更接近机器的级别。这也适用于字节码生成。
  • @Marco 但是 Java 的 JIT 确实解释字节码。它将其编译为机器码。
猜你喜欢
  • 2010-10-03
  • 2011-02-09
  • 2011-01-26
  • 1970-01-01
  • 2017-02-11
  • 2011-03-26
  • 2018-07-11
  • 2011-03-04
相关资源
最近更新 更多