在Java"虚拟机"中一直使用时,读取Python"虚拟机"似乎很少见.
两者都解释字节码; 为什么称其为虚拟机而另一个为解释器?
在这篇文章中,"虚拟机"指的是流程虚拟机,而不是Qemu或Virtualbox等系统虚拟机.过程虚拟机只是一个程序,它提供了一个通用的编程环境 - 一个可以编程的程序.
Java有一个解释器和一个虚拟机,Python有一个虚拟机和一个解释器."虚拟机"是Java中更常见的术语,而"解释器"是Python中更常见的术语,它与两种语言的主要区别有很大关系:静态类型(Java)与动态类型(Python).在此上下文中,"类型"是指 原始数据类型 - 表示数据的内存存储大小的类型.Java虚拟机很容易.它要求程序员指定每个变量的原始数据类型.这为Java字节码提供了足够的信息,不仅可以由Java虚拟机解释和执行,甚至可以编译成机器指令.Python虚拟机在某种意义上更复杂,它承担了在执行每个操作之前暂停的附加任务,以确定操作中涉及的每个变量或数据结构的原始数据类型.Python使程序员不再考虑原始数据类型,并允许操作在更高级别表达.这种自由的代价是表现."Interpreter"是Python的首选术语,因为它必须暂停检查数据类型,并且因为动态类型语言的相对简洁的语法非常适合交互式接口.构建交互式Java接口没有技术障碍,但尝试以交互方式编写任何静态类型的代码将是乏味的,所以它不是那样做的.
在Java世界中,虚拟机窃取节目,因为它运行的程序用一种语言编写,实际上可以编译成机器指令,结果是速度和资源效率.相对而言,Java字节码可以由Java虚拟机执行,其性能接近编译程序的性能.这是由于字节码中存在原始数据类型信息.Java虚拟机将Java放在自己的类别中:
便携式解释静态类型语言
下一个最接近的是LLVM,但LLVM在不同的级别运行:
便携式解释汇编语言
术语"字节码"在Java和Python中都使用,但并非所有字节码都是相同的.字节码只是编译器/解释器使用的中间语言的通用术语.甚至像gcc这样的C编译器也使用中间语言(或几个)来完成工作.Java字节码包含有关原始数据类型的信息,而Python字节码则不包含.在这方面,Python(以及Bash,Perl,Ruby等)虚拟机确实比Java虚拟机慢,或者更确切地说,它只需要做更多的工作.考虑包含在不同字节码格式中的信息非常有用:
llvm: cpu寄存器
Java:原始数据类型
Python:用户定义的类型
绘制一个真实世界的类比: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通常知道如果它不完全确信类型不会改变,就会回到常规虚拟机.
故事的寓意是原始数据类型信息对编译器/虚拟机非常有用.
最后,考虑到这一点:考虑这一点的Python程序:由Java实现的Python解释器/虚拟机执行的Python程序,运行在运行在iPhone上运行的qemu虚拟机中的LLVM中实现的Java解释器/虚拟机上.
永久链接
虚拟机是一种虚拟计算环境,具有一组特定的原子定义指令,这些指令独立于任何特定语言而受支持,并且通常被认为是沙箱本身.VM类似于特定CPU的指令集,并且倾向于在更基本的级别上工作,其中这些指令(或字节代码)的非常基本的构建块独立于下一个.指令仅基于虚拟机的当前状态确定性地执行,并且不依赖于该时间点的指令流中的其他地方的信息.
另一方面,解释器更复杂,因为它被定制为解析某些语法的流,该语法具有特定语言和必须在周围令牌的上下文中解码的特定语法.您不能孤立地查看每个字节甚至每一行,并确切知道下一步该做什么.语言中的标记不能像VM相关的指令(字节代码)那样孤立地进行.
Java编译器将Java语言转换为字节码流,与C编译器将C语言程序转换为汇编代码完全不同.另一方面,解释器并不真正将程序转换为任何定义良好的中间形式,它只是将程序操作作为解释源的过程.
对VM和解释器之间差异的另一个测试是你是否认为它与语言无关.我们所知道的Java VM并不是特定于Java的.您可以使用其他语言生成编译器,从而生成可在JVM上运行的字节代码.另一方面,我认为我们不会真的想到将Python以外的其他语言"编译"到Python中以供Python解释器解释.
由于解释过程的复杂性,这可能是一个相对缓慢的过程....专门解析和识别语言令牌等,并理解源的上下文,以便能够在解释器中执行执行过程.为了帮助加速这种解释语言,我们可以在这里定义更容易直接解释的预解析,预标记化源代码的中间形式.这种二进制形式仍然在执行时被解释,它只是从一种不那么易读的形式开始,以提高性能.但是,执行该表单的逻辑不是虚拟机,因为这些代码仍然不能孤立地进行 - 周围令牌的上下文仍然很重要,它们现在只是处于一种不同的计算机效率形式.
可能是不同术语的一个原因是人们通常会想到为python解释器提供原始的人类可读源代码,而不是担心字节码等等.
在Java中,您必须显式编译为字节码,然后只运行字节码,而不是VM上的源代码.
即使Python使用虚拟机,从用户的角度来看,大多数时候都可以忽略这个细节.
解释器,将源代码转换为一些有效的中间表示(代码)并立即执行此操作.
虚拟机,显式执行由编译器构建的存储的预编译代码,该编译器是解释器系统的一部分.
虚拟机的一个非常重要的特性是内部运行的软件仅限于虚拟机提供的资源.确切地说,它无法打破虚拟世界.考虑远程代码Java Applet的安全执行.
在python的情况下,如果我们保留pyc文件,如本文评论中所述,那么机制将变得更像VM,并且这个字节码执行得更快 - 它仍然会被解释,但是来自更加计算机友好的形式.如果我们从整体上看这个,PVM是Python Interpreter的最后一步.
底线是,当引用Python解释器时,它意味着我们将它作为一个整体来引用,当我们说PVM时,这意味着我们只是谈论Python解释器的一部分,即运行时环境.与Java类似,我们将不同的部分称为differentyl,JRE,JVM,JDK等.
更多信息,Wikipedia Entry:Interpreter和Virtual Machine.还有一个在这里.在这里,您可以找到应用程序虚拟机的比较.它有助于理解编译器,解释器和VM之间的区别.
术语解释器是一个可以追溯到早期shell脚本语言的遗留术语.随着"脚本语言"逐渐演变为全功能语言,并且其相应的平台变得更加复杂和沙盒化,虚拟机和解释器(在Python意义上)之间的区别非常小或根本不存在.
Python解释器仍然以与shell脚本相同的方式运行,因为它可以在没有单独的编译步骤的情况下执行.除此之外,Python的解释器(或Perl或Ruby)与Java的虚拟机之间的差异主要是实现细节.(有人可能会说Java比Python更完全沙箱化,但最终都通过本机C接口提供对底层架构的访问.)
它们之间没有真正的区别,人们只是遵循创作者所选择的惯例.
为了提供对“ 为什么要使用Java虚拟机,但要使用Python解释器 ” 这个问题的深刻答案,让我们尝试回到编译理论领域来作为讨论的起点。
程序编译的典型过程包括以下步骤:
词法分析。将程序文本拆分为有意义的“单词”(称为标记)(在此过程中,所有注释,空格,换行符等都将被删除,因为它们不会影响程序行为)。结果是令牌的有序流。
语法分析。从令牌流构建所谓的抽象语法树(AST)。AST建立令牌之间的关系,并因此定义程序评估的顺序。
语义分析。使用有关编程语言的类型和一组语义规则的信息来验证AST的语义正确性。(例如,a = b + c
从syntaxis的观点来看是正确的语句,但是从语义的观点来看,如果a
被声明为常量对象则是完全错误的)
中间代码生成。将AST序列化为与机器无关的“原始”操作的线性顺序流。实际上,代码生成器遍历AST并记录评估步骤的顺序。结果,从程序的树状表示中,我们获得了更加简单的列表状表示,其中保留了程序评估的顺序。
机器代码生成。机器独立的“原始”字节码形式的程序被转换为特定处理器体系结构的机器码。
好。现在让我们定义术语。
在该词的经典含义中,解释程序假定执行基于直接从程序文本产生的基于AST的程序评估。在这种情况下,程序将以源代码的形式分发,而解释器通常是通过动态方式(逐条陈述或逐行)由程序文本提供的。对于每个输入语句,解释器将构建其AST并立即对其进行评估以更改程序的“状态”。这是脚本语言演示的典型行为。考虑一下Bash,Windows CMD等。从概念上讲,Python也采用这种方式。
如果我们在解释器中生成与机器无关的二进制字节码的中间步骤时替换了基于AST的执行步骤,我们会将程序执行的整个过程分为两个单独的阶段:编译和执行。在那种情况下,以前是解释器的程序将成为字节码编译器,它将程序从文本形式转换为某种二进制形式。然后,程序以该二进制形式而不是源代码形式进行分发。在用户计算机上,该字节码被馈送到一个新的实体- 虚拟机中,该实体实际上会解释该字节码。因此,虚拟机也称为字节码解释器。但是请注意这里!古典口译员是文本解释器,但是虚拟机是二进制解释器!这是Java和C#采取的方法。
最后,如果将机器代码生成添加到字节码编译器中,则可以实现所谓的经典编译器。经典的编译器将程序源代码转换为特定处理器的机器代码。然后可以在目标处理器上直接执行该机器代码,而无需任何其他中介(没有任何类型的解释器,既没有文本解释器也没有二进制解释器)。
现在让我们回到最初的问题,并考虑Java与Python。
Java最初被设计为具有尽可能少的实现依赖性。它的设计基于“一次编写,随处运行”(WORA)的原则。为了实现它,Java最初被设计为一种编程语言,可以编译为独立于机器的二进制字节码,然后可以在支持Java的所有平台上执行而无需重新编译。您可以像基于WORA的C ++一样思考Java。实际上,Java比Python等脚本语言更接近C ++。但是与C ++相比,JavaC ++被设计成可以编译成二进制字节码,然后在虚拟机的环境中执行,而C ++被设计成可以由机器代码编译,然后由目标处理器直接执行。
Python最初被设计为一种脚本编程语言,可以解释脚本(按照编程语言规则编写的文本形式的程序)。因此,Python最初像Bash或Windows CMD一样,支持单行命令或语句的动态解释。出于同样的原因,巨蟒的初步实现了没有任何一种字节码编译器和这样的字节码内的执行虚拟机,而是从一开始的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一样,对于Java语言设计人员和Java程序开发人员而言,JVM罩下的JIT编译器的存在绝对是透明的。带有和不带有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虚拟机以较高的抽象级别运行,它以复杂的数据类型(对象)运行,并支持即席多态性,而字节码指令则暴露出复杂的行为,可以通过一系列多个本机机器码指令来表示。例如,Python支持无限范围数学。因此,Python VM被迫对可能的大整数使用长运算,对此运算结果可能会使机器字溢出。因此,Python中用于算术的一个字节码指令可以公开给Python VM内部的函数调用,而在JVM中,算术操作将公开给由一个或几条本机机器指令表示的简单操作。因此,Python VM被迫对可能的大整数使用长运算,对此运算结果可能会使机器字溢出。因此,Python中用于算术的一个字节码指令可以公开给Python VM内部的函数调用,而在JVM中,算术操作将公开给由一个或几条本机机器指令表示的简单操作。因此,Python VM被迫对可能的大整数使用长运算,对此运算结果可能会使机器字溢出。因此,Python中用于算术的一个字节码指令可以公开给Python VM内部的函数调用,而在JVM中,算术操作将公开给由一个或几条本机机器指令表示的简单操作。
结果,我们可以得出以下结论。Java虚拟机,但Python解释器是因为:
虚拟机一词假定二进制字节码解释,而术语解释器假定程序文本解释。
从历史上看,Java是为二进制字节码解释而设计和实现的,而Python最初是为程序文本解释而设计和实现的。因此,术语“ Java虚拟机”是历史悠久的,并且在Java社区中已得到很好的确立。同样,术语“ Python解释器”是历史悠久的,在Python社区中已得到很好的确立。人民倾向于延长传统并使用很久以前使用的相同术语。
最后,当前,对于Java,二进制字节码解释是程序执行的主要方式,而JIT编译只是可选的透明优化。对于Python,目前,程序文本解释是Python程序执行的主要方式,而编译为Python VM字节码只是一种可选的透明优化。
因此,Java和Python拥有的虚拟机都是二进制字节码解释器,这可能导致混淆,例如“ 为什么使用Java虚拟机,但是使用Python解释器?“这里的要点是,对于Python来说,虚拟机不是程序执行的主要手段或必要手段;它只是经典文本解释器的可选扩展。另一方面,虚拟机是核心并且不可避免Java程序执行生态系统的一部分,用于编程语言设计的静态或动态类型选择主要只影响虚拟机的抽象级别,但并不决定是否需要虚拟机,可以设计使用两种类型的系统进行编译的语言在虚拟机环境中进行,解释或执行,具体取决于其所需的执行模型。