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

Compilation to Bytecode, Java vs Python. What is the reason for the difference in time taken?

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

关于这个我有 2 个问题:

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

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

这里有我遗漏的东西吗? (我在 Java 工作的经验不多。)

更新:

我实际上首先对 python 运行 和后来的 运行 进行了时间分析,发现陈述 2 是错误的。当 运行 一个大的 python 文件时,有一个非常明显的区别。

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

的大文件
a = 5
b = 6
c = a*b
print(str(c))

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

第一个 运行 结果:

python large.py  1.49s user 0.33s system 97% cpu 1.868 total

第二个 运行 结果:

python large.py  0.20s user 0.08s system 90% cpu 0.312 total

删除__pycache__文件夹后:

python large.py  1.57s user 0.34s system 97% cpu 1.959 total

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

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

System.out.println("Hello World!");

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

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

所有这些检查都需要时间,最重要的是因为编译器必须从标准库中加载大量 class 定义,即使对于最微不足道的程序也是如此。

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

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

Python 编译器不关心其中某些查找是否在 运行 时失败。

另一个重要区别是 Java 编译器完全用 Java 编写,并在 运行 时编译为机器代码,而 CPython 的大部分实现是 ahead-of-time 编译的 C 代码。这意味着 Java 与 Python.

相比有一些“冷启动”问题

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

  • python 程序 运行s 在 45-50 毫秒内用 time python hello.py 测量。
  • Java 程序 没有提前编译成字节码 运行s 在 350-400 毫秒内用 time java Hello.java[ 测量=55=]
  • Java 程序在编译为字节码 运行s 后耗时 70-80 毫秒,用 time java Hello
  • 测量

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

hello.py:

print("hello world")

Hello.java:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}