为什么解释器每次运行程序时都要编译代码?
他们没有。解释器从不编译。它解释。如果它被编译,它将是一个编译器,而不是一个解释器。
解释器解释,编译器编译。
我的问题是关于所有解释型语言的,但为了更好地说明我的观点,我将使用 Java 作为示例。
没有解释型语言。使用解释器还是编译器纯粹是实现的一个特点,与语言完全无关。
每一种语言都可以由解释器或编译器来实现。绝大多数语言对每种类型都至少有一个实现。 (例如,C 和 C++ 有解释器,JavaScript、PHP、Perl、Python 和 Ruby 有编译器。)此外,大多数现代语言实现实际上结合了解释器和编译器(甚至多个编译器)。
语言只是一组抽象的数学规则。解释器是一种语言的几种具体实现策略之一。这两者生活在完全不同的抽象级别上。如果英语是一种类型化语言,那么术语“解释语言”将是类型错误。 “Python 是一种解释性语言”这句话不仅是错误的(因为错误意味着该陈述甚至是有意义的,即使它是错误的),它只是没有意义sense,因为一种语言永远不能被定义为“被解释的”。
我对 Java 的了解是,当程序员编写他们的代码时,他们必须将其编译成 Java 字节码,这类似于通用 Java 虚拟机架构的机器语言。然后他们可以将他们的代码分发到任何运行 Java 虚拟机 (JVM) 的机器上。
那不是真的。 Java 语言规范中没有任何内容需要字节码。那里甚至根本没有任何需要编译 Java 的东西。解释 Java 或将其编译为本机机器码是完全合法且符合规范的,事实上,两者都已完成。
另外,我很好奇:在本段中,您将 Java 描述为始终编译的语言,但在上一段中,您使用 Java 作为解释语言的示例。这没有任何意义。
JVM 只是一个程序,每次我运行我的程序时,它都会获取 java 字节码并编译它们(针对特定架构)。
同样,Java 虚拟机规范中没有任何关于编译或解释的内容,更不用说何时或多久编译一次代码了。
解释 JVML 字节码是完全合法且符合规范的,编译一次同样完全符合规范,事实上,两者都已完成。
根据我的理解(如果我在这里错了,请纠正我)如果我运行我的代码,JVM 将即时编译它,我的机器将运行编译后的指令,当我关闭程序时,所有的编译工作将是丢了,只能再做一次,我想第二次运行我的程序。
这完全取决于您使用的 JVM、您使用的 JVM 的哪个版本,有时甚至取决于特定的环境和/或命令行参数。
一些 JVM 解释字节码(例如 Sun 的 JVM 的旧版本)。某些版本编译字节码一次(例如 Excelsior.JET)。一些版本在开始时解释字节码,在程序运行时收集分析信息和统计信息,使用这些数据来查找所谓的“热点”(即最常执行的代码,因此最受益的代码)从加速它)然后使用分析数据编译这些热点以进行优化(例如IBM J9,Oracle HotSpot)。有些人使用类似的技巧,但有一个非优化的快速编译器而不是解释器。一些缓存并重用已编译的本机机器代码(例如现已废弃的 JRockit)。
这也是一般解释型语言速度慢的原因,因为它们每次都必须即时编译。
谈论一种语言是慢是快是没有意义的。语言不慢也不快。语言只是一张纸。
在特定环境下,在特定硬件上的特定环境中,在特定语言的特定执行引擎的特定版本上运行的特定代码段可能会或可能不会比另一段特定代码慢在另一特定环境下,在另一特定硬件上的另一特定环境中,在另一特定语言的另一特定执行引擎的另一特定版本上运行,但这与语言无关。
一般来说,性能主要是钱的问题,在较小程度上是执行环境的问题。在 MacPro 上运行用 Microsoft Visual C++ 在 Windows 上编译的 C++ 编写的特定代码确实可能比在 MacPro 上运行由 YARV 在 Windows 上执行的用 Ruby 编写的类似代码更快。
然而,主要原因是微软是一家巨大的公司,在 Visual C++ 中投入了大量资金、研究、工程、人力和其他资源,而 YARV 主要是志愿者的努力。同时优化了大多数主流操作系统如Windows、macOS、Linux、各种BSD和Unices等以及大多数主流CPU架构如AMD64、x86、PowerPC、ARM、SPARC、MIPS、Super-H等。用于加速类 C 语言的程序,并且对类 Smalltalk 语言的优化要少得多。事实上,某些功能甚至会主动伤害它们(例如,虚拟内存会显着增加垃圾收集延迟,即使它在内存管理语言中完全没用)。
然而,这一切对我来说毫无意义。为什么不在我的机器上下载java字节码,让JVM为我的特定架构编译一次并创建一个可执行文件,然后下次我想运行程序时,我只运行编译后的可执行文件。
如果这是你想要的,没有人会阻止你。例如,这正是 Excelsior.JET 所做的。没有人强迫您使用 IBM J9 或 Oracle HotSpot。
我知道在编译 JVM 时会进行一些巧妙的动态优化;然而,它们的目的不只是为了弥补解释机制的缓慢吗?
那些动态优化只有可能正是因为它们是动态的。编程中有几个根本不可能的结果严重限制了静态提前编译器可以执行的优化类型。停机问题、赖斯定理、函数问题等。
例如,Java 等语言中的内联需要类层次结构分析。换句话说,编译器需要证明一个方法没有被覆盖,以便能够内联它。事实证明,动态加载语言中的类层次结构分析相当于解决停机问题。因此,静态编译器只能在有限的情况下内联,在一般情况下它可以不判断方法是否被覆盖。
在运行时编译代码的动态 JIT 编译器不需要证明方法未被覆盖。它不需要在运行时静态计算类层次结构。类层次结构就在那里:它可以简单地看:方法是否被覆盖?因此,动态编译器在比静态编译器更多的情况下可以内联。
但还有更多。动态编译器也可以执行去优化。现在,您可能想知道:为什么要de-优化?为什么让代码变得更糟?好吧,原因如下:如果你知道你可以反优化,那么你可以根据 猜测 进行优化,如果你猜错了,那么你可以再次删除优化。 p>
与我们的内联示例保持一致:与静态编译器不同,我们的动态编译器可以 100% 准确地确定方法是否被覆盖。然而,它不一定知道被覆盖的方法是否会被调用。如果被覆盖的方法永远不会被调用,那么内联超类方法仍然是安全且合法的!所以,我们聪明的动态编译器可以做的是内联超类方法无论如何,但在开头放了一点类型检查,以确保如果接收者对象曾经是子类类型,我们将反优化回未内联的版本。这称为推测内联,是静态 AOT 编译器根本上无法做到的。
多态内联缓存是现代高性能语言执行引擎(如 HotSpot、Rubinius 或 V8)执行的更为复杂的优化。
我的意思是,如果 JVM 必须编译一次,运行多次,那么这不会超过 JVM 所做的优化的加速吗?
这些动态优化基本上对于静态优化器来说是不可能的。