在 Java 中一直使用“虚拟机”时,似乎很少阅读 Python“虚拟机”。
两者都解释字节码;为什么将一个称为虚拟机而另一个称为解释器?
在这篇文章中,“虚拟机”是指进程虚拟机,而不是像 Qemu 或 Virtualbox 这样的系统虚拟机。进程虚拟机只是一个提供通用编程环境的程序——一个可以编程的程序。
Java 有解释器和虚拟机,Python 有虚拟机和解释器。“虚拟机”在 Java 中是一个更常见的术语,而“解释器”在 Python 中是一个更常见的术语,这与两种语言之间的主要区别有很大关系:静态类型 (Java) 与动态类型 (Python)。在这种情况下,“类型”指的是 原始数据类型——表明数据在内存中存储大小的类型。Java虚拟机很容易。它要求程序员指定每个变量的原始数据类型。这为 Java 字节码提供了足够的信息,不仅可以被 Java 虚拟机解释和执行,甚至可以编译成机器指令. Python 虚拟机更复杂,因为它在执行每个操作之前承担额外的暂停任务,以确定操作中涉及的每个变量或数据结构的原始数据类型。Python 将程序员从原始数据类型的思维中解放出来,并允许在更高层次上表达操作。这种自由的代价是性能。“解释器”是 Python 的首选术语,因为它必须停下来检查数据类型,还因为动态类型语言相对简洁的语法非常适合交互式接口。构建交互式 Java 接口没有技术障碍,但尝试以交互方式编写任何静态类型的代码将是乏味的,所以不是那样做的。
在 Java 世界中,虚拟机抢尽风头,因为它运行用一种语言编写的程序,实际上可以编译成机器指令,结果是速度和资源效率。Java字节码可以由Java虚拟机执行,性能相对而言接近编译程序。这是由于字节码中存在原始数据类型信息。Java 虚拟机将 Java 归入一个自己的类别:
可移植解释静态类型语言
下一个最接近的是 LLVM,但 LLVM 在不同的级别上运行:
可移植的解释性汇编语言
Java 和 Python 都使用术语“字节码”,但并非所有字节码都是平等的。字节码只是编译器/解释器使用的中间语言的通用术语。甚至像 gcc 这样的 C 编译器也使用一种(或几种)中间语言来完成工作。Java 字节码包含有关原始数据类型的信息,而 Python 字节码不包含。在这方面,Python(以及 Bash、Perl、Ruby 等)虚拟机确实从根本上比 Java 虚拟机慢,或者更确切地说,它只是有更多的工作要做。考虑不同字节码格式中包含哪些信息很有用:
打一个现实世界的类比:LLVM 处理原子,Java 虚拟机处理分子,Python 虚拟机处理材料。由于一切最终都必须分解为亚原子粒子(真机操作),因此 Python 虚拟机的任务最为复杂。
静态类型语言的解释器/编译器与动态类型语言的解释器/编译器没有相同的包袱。静态类型语言的程序员必须弥补这一不足,而回报就是性能。然而,正如所有非确定性函数都是秘密确定性的一样,所有动态类型语言都是秘密静态类型的。因此,这两个语言家族之间的性能差异应该会在 Python 更名为 HAL 9000 时趋于平稳。
Python等动态语言的虚拟机实现了一些理想化的逻辑机器,不一定与任何真实的物理硬件非常接近。相比之下,Java 虚拟机在功能上更类似于经典的 C 编译器,只是它不是发出机器指令,而是执行内置例程。在 Python 中,整数是一个带有一堆属性和方法的 Python 对象。在 Java 中,int 是指定的位数,通常为 32。这并不是一个公平的比较。Python 整数确实应该与 Java Integer 类进行比较。Java 的“int”原始数据类型无法与 Python 语言中的任何内容进行比较,因为 Python 语言只是缺少这一层原始数据,Python 字节码也是如此。
因为 Java 变量是显式类型的,所以可以合理地期望Jython性能与 cPython处于同一水平。另一方面,用 Python 实现的 Java 虚拟机几乎可以保证比泥巴慢。不要指望 Ruby、Perl 等会更好。他们不是为此而设计的。它们是为“脚本”而设计的,这就是所谓的动态语言编程。
在虚拟机中发生的每一个操作最终都必须命中真实的硬件。虚拟机包含预编译的例程,这些例程足够通用,可以执行任何逻辑操作的组合。虚拟机可能不会发出新的机器指令,但它肯定会以任意复杂的顺序一遍又一遍地执行自己的例程。Java 虚拟机、Python 虚拟机和所有其他通用虚拟机在某种意义上是相同的,因为它们可以被诱使执行您可以梦想的任何逻辑,但它们在执行任务方面是不同的承担,以及他们留给程序员的任务。
Psyco for Python 不是一个完整的 Python 虚拟机,而是一个即时编译器,它在它认为可以编译几行代码的地方劫持常规 Python 虚拟机——主要是在它认为某些原始类型的地方循环即使值随着每次迭代而变化,变量也将保持不变。在这种情况下,它可以放弃常规虚拟机的一些持续类型确定。但是,您必须小心一点,以免您将类型从 Psyco 脚下拉出来。然而,如果 Pysco 不能完全确定类型不会改变,Pysco 通常只知道回退到常规虚拟机。
这个故事的寓意是原始数据类型信息对编译器/虚拟机非常有帮助。
最后,从长远来看,考虑一下:一个 Python 程序由 Python 解释器/虚拟机执行,该虚拟机在 Java 中运行,在 Java 解释器/虚拟机上运行,在 LLVM 中运行,在 iPhone 上运行的 qemu 虚拟机中运行。
虚拟机是一个虚拟计算环境,具有一组特定的原子定义明确的指令,这些指令独立于任何特定语言而受到支持,并且通常被认为是其自身的沙箱。VM 类似于特定 CPU 的指令集,并且倾向于在更基本的级别上工作,这些指令(或字节码)的非常基本的构建块独立于下一个。一条指令仅根据虚拟机的当前状态确定性地执行,而不依赖于该时间点指令流中其他地方的信息。
另一方面,解释器更复杂,因为它适合解析某些语法流,这些语法属于特定语言和特定语法,必须在周围标记的上下文中解码。您不能孤立地查看每个字节甚至每一行,并确切地知道下一步该做什么。语言中的标记不能像它们相对于 VM 的指令(字节码)那样孤立。
Java 编译器将 Java 语言转换为字节码流,这与 C 编译器将 C 语言程序转换为汇编代码没有什么不同。另一方面,解释器并没有真正将程序转换为任何定义良好的中间形式,它只是将程序动作作为解释源代码的过程。
VM 和解释器之间差异的另一个测试是您是否认为它与语言无关。我们所知道的 Java VM 并不是真正的 Java 特定的。您可以使用其他语言制作编译器,生成可以在 JVM 上运行的字节码。另一方面,我认为我们不会真正考虑将 Python 以外的其他语言“编译”成 Python 以供 Python 解释器进行解释。
由于解释过程的复杂性,这可能是一个相对缓慢的过程......特别是解析和识别语言标记等,并了解源的上下文以便能够在解释器中承担执行过程。为了帮助加速此类解释语言,我们可以在这里定义更容易直接解释的预解析、预标记源代码的中间形式。这种二进制形式仍然在执行时被解释,它只是从一种人类可读性差得多的形式开始以提高性能。然而,执行该形式的逻辑不是虚拟机,因为这些代码仍然不能孤立地看待——周围令牌的上下文仍然很重要,它们只是现在处于一种不同的计算机效率更高的形式。
使用不同术语的一个原因可能是人们通常认为向 python 解释器提供原始的人类可读源代码,而不用担心字节码和所有这些。
在 Java 中,您必须显式编译为字节码,然后只运行字节码,而不是 VM 上的源代码。
尽管 Python 在幕后使用了虚拟机,但从用户的角度来看,大部分时间都可以忽略这个细节。
解释器,将源代码翻译成一些有效的中间表示(代码)并立即执行。
虚拟机,显式执行由编译器构建的存储的预编译代码,该编译器是解释器系统的一部分。
虚拟机的一个非常重要的特点是,里面运行的软件,受限于虚拟机提供的资源。准确地说,它无法脱离它的虚拟世界。想想远程代码的安全执行,Java Applet。
在 python 的情况下,如果我们保留pyc文件,正如这篇文章的评论中所提到的,那么该机制将变得更像一个虚拟机,并且这个字节码执行得更快——它仍然会被解释,但从一个对计算机更友好的形式. 如果我们从整体上看,PVM 是 Python Interpreter 的最后一步。
底线是,当提到 Python Interpreter 时,这意味着我们将其作为一个整体来引用,而当我们说 PVM 时,这意味着我们只是在谈论 Python Interpreter 的一部分,即运行时环境。与Java类似,我们指代不同的部分differyl、JRE、JVM、JDK等。
有关更多信息,请参阅 Wikipedia Entry: Interpreter和Virtual Machine。这里还有一个。在这里您可以找到应用程序虚拟机的比较。它有助于理解编译器、解释器和虚拟机之间的区别。
为了对“为什么是 Java 虚拟机,而使用 Python 解释器? ”这个问题提供一个深刻的答案,让我们试着回到编译理论领域作为讨论的起点。
程序编译的典型过程包括以下步骤:
a = b + c
,则从语义的角度来看是完全错误的)a
行。现在让我们定义这些术语。
解释器,在该词的经典含义中,假定基于直接从程序文本产生的 AST 的程序评估执行。在这种情况下,程序以源代码的形式分发,解释器由程序文本提供,通常以动态方式(逐语句或逐行)。对于每个输入语句,解释器构建它的 AST 并立即评估它以改变程序的“状态”。这是脚本语言所展示的典型行为。例如考虑 Bash、Windows CMD 等。从概念上讲,Python 也采用这种方式。
如果我们在解释器中生成与机器无关的中间二进制字节码步骤替换基于 AST 的执行步骤,我们会将程序执行的整个过程分成两个独立的阶段:编译和执行。在这种情况下,以前的解释器将变成字节码编译器,它将程序从文本形式转换为某种二进制形式。然后程序以二进制形式分发,而不是以源代码的形式分发。在用户机器上,该字节码被输入一个新实体——虚拟机,它实际上解释了该字节码。因此,虚拟机也被称为字节码解释器。但是把你的注意力放在这里!古典口译员是文本解释器,但虚拟机是二进制解释器!这是 Java 和 C# 采用的方法。
最后,如果我们将机器代码生成添加到字节码编译器中,我们就可以实现我们所说的经典编译器。经典编译器将程序源代码转换为特定处理器的机器代码。然后,该机器代码可以直接在目标处理器上执行,无需任何额外的中介(无需任何类型的解释器,既没有文本解释器,也没有二进制解释器)。
现在让我们回到最初的问题,考虑 Java 与 Python。
Java最初被设计为具有尽可能少的实现依赖项。它的设计基于“一次编写,随处运行”(WORA)的原则。为了实现它,Java最初被设计为一种编程语言,可以编译成与机器无关的二进制字节码,然后可以在所有支持Java的平台上执行,而无需重新编译。您可以像考虑基于 WORA 的C++一样考虑Java。实际上,Java更接近于C++,而不是像Python这样的脚本语言。但与C++相比,Java被设计为编译成二进制字节码,然后在虚拟机环境中执行,而C++被设计为编译成机器码,然后直接由目标处理器执行。
Python最初被设计为一种脚本编程语言,它解释脚本(按照编程语言规则编写的文本形式的程序)。因此,Python 最初支持单行命令或语句的动态解释,就像 Bash 或 Windows CMD 所做的那样。出于同样的原因,Python 的初始实现没有任何类型的字节码编译器和用于执行此类字节码的虚拟机,但从一开始,Python就需要能够理解和评估 Python 程序文本的解释器。
正因为如此,历史上,Java开发者倾向于谈论Java 虚拟机(因为最初,Java是作为Java字节码编译器和字节码解释器的包JVM出现的),而Python开发者倾向于谈论Python解释器(因为最初Python有不是任何虚拟机,而是一种经典的文本解释器,它直接执行程序文本,无需任何类型的编译或转换为任何形式的二进制代码)。
目前,Python 也有底层的虚拟机,可以编译和解释 Python 字节码。这一事实对“为什么是 Java 虚拟机,而是 Python 解释器?并且程序将展示完全相同的行为并从相同的输入产生相同的输出。唯一可观察到的区别是程序执行的速度和解释器消耗的内存量。因此,Python 中的虚拟机并不是语言设计中不可避免的一部分,而只是主要 Python 解释器的一个可选扩展。
可以以类似的方式考虑 Java。Java底层有一个JIT编译器,可以选择性地将Java类的方法编译成目标平台的机器码,然后直接执行。但!Java 仍然使用字节码解释作为 Java 程序执行的主要方式。与专门利用底层虚拟机作为优化技术的 Python 实现一样,Java 虚拟机专门使用即时编译器进行优化。同样,只是因为直接执行机器码至少比解释 Java 字节码快十倍。和 Python 一样,JVM 底层 JIT 编译器的存在对于 Java 语言设计者和 Java 程序开发者来说都是绝对透明的。使用和不使用 JIT 编译器的 JVM 都可以实现相同的 Java 编程语言。同样,相同的程序可以在内部有或没有 JIT 的 JVM 中执行,并且相同的程序将展示完全相同的行为,并从两个 JVM(有和没有 JIT)上的相同输入产生相同的输出。和 Python 一样,它们之间唯一可观察到的区别在于执行速度和 JVM 消耗的内存量。最后,就像 Python 一样,Java 中的 JIT 也不是语言设计中不可避免的一部分,而只是主要 JVM 实现的可选扩展。并且相同的程序将展示完全相同的行为,并从两个 JVM(使用和不使用 JIT)上的相同输入产生相同的输出。和 Python 一样,它们之间唯一可观察到的区别在于执行速度和 JVM 消耗的内存量。最后,就像 Python 一样,Java 中的 JIT 也不是语言设计中不可避免的一部分,而只是主要 JVM 实现的可选扩展。并且相同的程序将展示完全相同的行为,并从两个 JVM(使用和不使用 JIT)上的相同输入产生相同的输出。和 Python 一样,它们之间唯一可观察到的区别在于执行速度和 JVM 消耗的内存量。最后,就像 Python 一样,Java 中的 JIT 也不是语言设计中不可避免的一部分,而只是主要 JVM 实现的可选扩展。
从 Java 和 Python 虚拟机的设计和实现的角度来看,它们有很大的不同,而(注意!)两者仍然是虚拟机。JVM是一个基本操作简单、指令调度成本高的低级虚拟机的例子。反过来,Python 是一个高级虚拟机,指令表现出复杂的行为,指令调度成本并不那么重要。Java 以非常低的抽象级别运行。JVM 在少量定义良好的原始类型集上运行,并且在字节码指令和本机机器码指令之间具有非常紧密的对应关系(通常是一对一的)。相反,Python 虚拟机运行在高抽象级别,它运行复杂的数据类型(对象)并支持 ad-hoc 多态性,而字节码指令暴露了复杂的行为,可以用一系列多条本机机器码指令来表示。例如,Python 支持无限范围数学。因此,Python VM 被迫利用长算法来处理可能会导致机器字溢出的大整数。因此,Python 中用于算术的一条字节码指令可以暴露在 Python VM 内部的函数调用中,而在 JVM 中,算术运算将暴露为由一条或几条本机机器指令表示的简单操作。因此,Python VM 被迫利用长算法来处理可能会导致机器字溢出的大整数。因此,Python 中用于算术的一条字节码指令可以暴露在 Python VM 内部的函数调用中,而在 JVM 中,算术运算将暴露为由一条或几条本机机器指令表示的简单操作。因此,Python VM 被迫利用长算法来处理可能会导致机器字溢出的大整数。因此,Python 中用于算术的一条字节码指令可以暴露在 Python VM 内部的函数调用中,而在 JVM 中,算术运算将暴露为由一条或几条本机机器指令表示的简单操作。
因此,我们可以得出下一个结论。Java 虚拟机但 Python 解释器是因为:
因此,Java 和 Python 的虚拟机都是二进制字节码解释器,这会导致诸如“为什么是 Java 虚拟机,但是 Python 解释器?”。这里的重点是,对于Python来说,虚拟机并不是程序执行的主要或必要手段;它只是经典文本解释器的可选扩展。另一方面,虚拟机是核心,不可避免Java 程序执行生态系统的一部分。编程语言设计的静态或动态类型选择主要影响虚拟机抽象级别,但不决定是否需要虚拟机。使用这两种类型系统的语言可以设计为可编译、解释或在虚拟机环境中执行,具体取决于它们所需的执行模型。
术语解释器是可以追溯到早期的 shell 脚本语言的遗留术语。随着“脚本语言”已经发展为功能齐全的语言,并且其相应的平台变得更加复杂和沙盒化,虚拟机和解释器(在 Python 意义上)之间的区别非常小或不存在。
Python 解释器仍然以与 shell 脚本相同的方式运行,因为它可以在没有单独的编译步骤的情况下执行。除此之外,Python 的解释器(或 Perl 或 Ruby 的)和 Java 的虚拟机之间的区别主要在于实现细节。(有人可能会争辩说,Java 比 Python 更完全沙盒化,但两者最终都通过原生 C 接口提供对底层架构的访问。)
它们之间没有真正的区别,人们只是遵循创作者选择的约定。
不要忘记 Python 有可用于 x86 的 JIT 编译器,这进一步混淆了这个问题。(见心理)。
只有在讨论 VM 的性能问题时,对“解释性语言”的更严格解释才会有用,例如,与 Python 相比,Ruby 被(是?)认为速度较慢,因为它是一种解释性语言,与 Python 不同——在其他方面话,上下文就是一切。
Python 可以解释代码而无需将其编译为字节码。Java 不能。
Python 是一种解释语言,而不是编译语言,尽管由于字节码编译器的存在,区别可能会很模糊。这意味着可以直接运行源文件,而无需显式创建随后运行的可执行文件。
(来自文档)。
在java中,每个文件都必须编译成一个.class
文件,然后在JVM上运行。相反,python 会由您的主脚本导入,以帮助加快这些文件的后续使用。
但是,在典型情况下,大多数 python(至少是 CPython)代码在模拟堆栈机器中运行,它的指令与 JVM 的指令几乎相同,因此没有太大区别。
然而,造成这种差异的真正原因是,从一开始,java 就将自己标榜为“可移植、可执行的字节码”,而 python 将自己标榜为带有 REPL 的动态解释语言。名字贴!
我认为两者之间的界限很模糊,人们大多围绕“解释器”一词的含义以及该语言与“解释器...编译器”范围的每一方的接近程度争论不休。然而,没有人能做到 100%。我认为编写具有任何价值的 Java 或 Python 实现都很容易。
目前,Java 和 Python 都有虚拟机和字节码,虽然其中一个通过具体的值大小(如 32 位整数)进行操作,而另一个则必须确定每个调用的大小,我认为这并没有定义术语之间的边界。
Python没有官方定义的字节码,它只存在于内存中的论点也不能说服我,只是因为我打算开发只能识别Python字节码的设备,编译部分将在浏览器JS机器中完成。
性能仅与具体实现有关。我们不需要知道对象的大小就可以使用它,最后,在大多数情况下,我们使用的是结构,而不是基本类型。可以通过重用现有对象来优化 Python VM,从而消除在表达式计算期间每次创建新对象的需要。一旦完成,计算两个整数之和之间就没有全局性能差异,这就是 Java 的亮点。
两者之间没有致命的区别,只有一些与最终用户无关的实现细微差别和缺乏优化,也许在她开始注意到性能滞后的时候,但同样是实现而不是架构问题。
首先,您应该了解编程或计算机科学一般不是数学,我们对我们经常使用的大多数术语没有严格的定义。
现在回答你的问题:
什么是口译员(计算机科学)
它按最小的可执行单元翻译源代码,然后执行该单元。
什么是虚拟机
在 JVM 的情况下,虚拟机是一个包含解释器、类加载器、垃圾收集器、线程调度器、JIT 编译器和许多其他东西的软件。
如您所见,解释器是一部分或 JVM,而整个 JVM 不能称为解释器,因为它包含许多其他组件。
为什么在谈论 python 时使用“解释器”这个词
使用 java,编译部分是明确的。另一方面,python 并不像 java 那样明确其编译和解释过程,从最终用户的角度来看,解释是用于执行 python 程序的唯一机制
不,它们都不解释字节码。
如果您使用 pypy 运行,Python 只会解释字节码。否则,它将被编译成 C 并在该级别进行解释。
Java 编译为字节码。
对于提到 python 不需要生成字节码的帖子,我不确定这是真的。似乎 Python 中的所有可调用对象都必须具有.__code__.co_code
包含字节码的属性。我没有看到一个有意义的理由将 python 称为“未编译”,只是因为编译的工件可能无法保存;并且通常不是通过设计在 Python 中保存的,例如,所有理解都会为其输入编译新字节码,这就是理解变量范围在运行 python 脚本和使用 pdb之间compile(mode='exec, ...)
和编译之间不一致的原因compile(mode='single', ...)
实际上,HotSpot 运行时被称为虚拟机而 CPython 仅被称为解释器可能是有原因的
首先,CPython 只是普通的、基于堆栈的字节码解释器。您将 Python 操作码输入其中,CPython 中的软件堆栈机器会评估您的代码,就像普通的解释器一样。
Java HotSpot 运行时不同。首先,Java 有 3 个即时编译器,C1、C2 和一个尚未使用的实验性编译器。但这不是主要原因。JVM 内部的解释器是一种非常特殊的解释器,称为模板解释器。JVM 中的模板解释器包含一个巨大的数组列表,而不是像 CPython(实际上几乎所有其他解释器都这样做)那样直接在大量操作码 switch case 语句中执行字节码。它包含什么?字节码和本机 CPU 指令的键值对!数组列表在启动时是空的,并且充满了指向本地机器语言的字节码映射,以便在应用程序启动之前直接在硬件上运行,这意味着 JVM 内的“解释器”不是 t 实际上是一个解释器 - 它实际上是一个折扣编译器!当 Java 字节码运行时,“解释器”只是简单地将输入字节码直接映射到本机机器语言并直接执行本机映射,而不是在软件中实现。我不确定为什么 JVM 是这样制作的,但我怀疑它是为了轻松地无缝执行“解释”代码和 JIT 编译代码,并提高速度/性能。如果您将没有 JIT 的 JVM 与 CPython 或大多数其他解释器进行比较,它可能仍然会领先于它们,因为它的巧妙设计据我所知是其他语言以前没有使用过的。简单地将输入字节码直接映射到本机机器语言并直接执行本机映射,而不是在软件中实现它。我不确定为什么 JVM 是这样制作的,但我怀疑它是为了轻松地无缝执行“解释”代码和 JIT 编译代码,并提高速度/性能。如果您将没有 JIT 的 JVM 与 CPython 或大多数其他解释器进行比较,它可能仍然会领先于它们,因为它的巧妙设计据我所知是其他语言以前没有使用过的。简单地将输入字节码直接映射到本机机器语言并直接执行本机映射,而不是在软件中实现它。我不确定为什么 JVM 是这样制作的,但我怀疑它是为了轻松地无缝执行“解释”代码和 JIT 编译代码,并提高速度/性能。如果您将没有 JIT 的 JVM 与 CPython 或大多数其他解释器进行比较,它可能仍然会领先于它们,因为它的巧妙设计据我所知是其他语言以前没有使用过的。