编译为字节码,Java 与 Python。所用时间不同的原因是什么?

spr*_*ksh 4 python java jvm cpython compilation

Java 和 python(仅讨论 CPython)分别被解释为 Java 和 CPython 字节码。然后,这两个字节码都由它们各自的虚拟机(JVM 和 Cpython VM)解释。(这里我忽略了在 10K 运行后开始的 JIT 编译部分。)

我对此有两个问题:

  1. 与 python 相比,为什么 Java 编译为 Java 字节码需要这么多时间?在 java 中,编译是一个明确的步骤,而在 python 中它发生在运行时。
  2. 为什么在第一次运行中编译到 CPython 字节码并缓存在所有连续运行中使用的 .pyc 文件中时,python 的第一次运行和第 n 次运行之间没有明显区别。这个字节码编译真的是python中几乎零成本的任务吗?

虽然它在运行时扮演着重要的角色,但我认为静态和动态类型在编译过程中不应该扮演太大的角色,也不应该是造成这种时间差异的唯一原因。另外,我认为在这两个实现中,在字节码生成期间进行了一些优化。

有什么我在这里想念的吗?(我没有太多的 Java 工作经验。)

更新:

我实际上对 python 第一次运行和后来的运行做了时间分析,发现语句 2 是错误的。运行大型python文件时有非常明显的区别。

方法很简单。创建了一个包含重复行的大文件

a = 5
b = 6
c = a*b
print(str(c))
Run Code Online (Sandbox Code Playgroud)

然后将其导入文件large.py并运行time python large.py

首次运行结果:

python large.py  1.49s user 0.33s system 97% cpu 1.868 total
Run Code Online (Sandbox Code Playgroud)

第二次运行结果:

python large.py  0.20s user 0.08s system 90% cpu 0.312 total
Run Code Online (Sandbox Code Playgroud)

删除 __pycache__ 文件夹后:

python large.py  1.57s user 0.34s system 97% cpu 1.959 total
Run Code Online (Sandbox Code Playgroud)

所以基本上在 python 中,编译为字节码是一个昂贵的过程,只是它不像在 java 中那么昂贵。

Jon*_*oni 7

Java 字节码编译器必须比 Python 字节码编译器做更多的检查。为了说明这一点,请从“hello world”程序中取出这一行:

System.out.println("Hello World!");
Run Code Online (Sandbox Code Playgroud)

要编译这行代码,编译器必须找出其所有部分的含义。这比听起来更复杂:System可能是一个包。或者它可以是一个类,或者在代码所在的同一个包中,或者在一个导入的包中,或者在java.lang. 所以编译器必须按顺序检查所有这些选项。一旦找到System该类,它就必须检查其访问修饰符是否允许这种使用。

之后,编译器必须弄清楚什么out是:它是嵌套类还是类成员,以及它的访问修饰符是什么?编译器发现它是类型的静态成员变量PrintStream。然后它必须对println. 编译器在知道所有这些之前不能为这行代码发出任何代码,因为生成的字节码根据所涉及对象的类型而不同。

所有这些检查都需要时间,最重要的是,即使对于最简单的程序,编译器也必须从标准库中加载大量类定义。

相比之下,Python 字节码编译器只需要解析行,就可以立即生成代码,无需查看额外的模块。在 Python 中,代码将被编译为:

  • 从当前范围(LOAD_NAME)中查找“系统”对象
  • 从系统(LOAD_ATTR)中查找“out”属性
  • 从“out”中查找“println”(LOAD_METHOD)
  • 生成代码来调用它(CALL_METHOD)

Python 编译器并不关心这些查找中的一些是否在运行时失败。

另一个重要的区别是 Java 编译器完全用 Java 编写,并在运行时编译为机器代码,而大部分 CPython 实现是提前编译的 C 代码。这意味着与 Python 相比,Java 存在一些“冷启动”问题。

更新:从 Java 9 开始,您可以直接从源代码运行 Java 程序,而无需将其编译为字节码。运行一个简单的“hello world”程序可以让您了解通过提前将 Java 编译为字节码可以节省多少,即使对于一个简单的程序也是如此:

  • python 程序运行时间为 45-50 毫秒,用time python hello.py.
  • 未提前编译为字节码的 Java 程序运行时间为 350-400 毫秒,用time java Hello.java
  • 编译成字节码后的Java程序运行时间为70-80毫秒,用 time java Hello

免责声明:没有遵循科学方法或进行统计分析,所以对此持保留态度。测试环境:Python 3.8.5,Java 11.0.8,Fedora 32,Intel i7 8750H CPU

你好.py:

System.out.println("Hello World!");
Run Code Online (Sandbox Code Playgroud)

你好.java:

print("hello world")
Run Code Online (Sandbox Code Playgroud)

  • `System` 也可以是一个变量,这是首先检查的内容。然后,当没有找到变量和本地类时,将检查所有“import”语句,因为当没有显式导入匹配时,隐式“java.lang”是后备。值得一提的是,为了找到 `println` 的最佳候选方法,必须加载 `PrintStream` 的所有超类型。过去有一些版本采用了捷径,当声明的类型完全匹配(`println(String)`)时不加载超类型,但据我所知,当前的实现放弃了这个捷径。 (2认同)