为什么Python比Java慢?

如果 CPython 解释 Python 源代码并将其保存为 .pyc 中的字节码,而 java 只使用编译器做类似的事情,那么在下一次请求代码时,解释器将不会解释源代码,而是使用先前解释过的 .pyc 文件,为什么 Python 在这里速度较慢?

PVM 和 JVM 都会读取以前保存的字节码,为什么 JVM 的执行速度比 PVM 快得多?

Python 和 Java 都能将源文件编译成字节码,它们的区别在于如何运行字节码。

在这两种语言中,字节码基本上都是文本源代码的二进制表示,而不是可以在 CPU 上运行的汇编程序。你需要一个不同的程序来接受字节码并运行它。

那它是如何运行的?

Python
Python 有一个解释器,即一个保存 Python 程序 "世界模型 "的程序(哪些模块被导入、哪些变量存在、哪些对象存在......),它通过逐个加载字节码并分别执行来运行程序。

  • 这意味着,诸如 y = x + 1 这样的语句会以 "加载常数 1"、"加载 x"、"将两个值相加"、"将结果存储在 y 中 "等一系列操作的形式执行。
  • 这些操作中的每一个都是通过函数调用来实现的,函数调用会在 C 语言中执行一些操作,并经常读取和更新字典结构。

这样做很慢,而且操作越少越慢。

这就是为什么 Python 中的数值代码很慢的原因:Python 中的数值运算将单条指令转换为多个函数调用,因此在这种类型的代码中,Python 甚至比其他语言慢 100 倍。

Java
Java 将字节码编译成机器码。你看不到它,因为它发生在运行时(称为 JIT),但它确实发生了。由于 Java 也知道 y = x + 1 中的 x 是整数,因此它可以使用一条 CPU 指令来执行这一行。

不仅如此,JVM 还进行自适应优化。它的工作原理是保存条件分支统计信息,并在确定某些分支条件比其他条件更频繁地出现时动态地重新编译部分代码。重新编译的代码针对最常见的分支条件进行了优化(即,无论何时发生,都不跳转),并且只有不太常见的条件才会导致性能损失。

Java 实现还更进一步;它们将以解释模式运行,在分析运行时行为后立即启动并生成本机代码。有些还可以保存跨进程重新启动,以便在下次运行时更快地预热。如Graal等。 

实际上,Python 的一个实现也能进行 JIT 编译:叫 PyPy,平均比 CPython 快五倍,具体快多少取决于你用它来做什么。它可以运行所有纯 Python 代码,但在使用某些库时仍有问题。

其他原因:

  1. 出于性能原因,Java 历史上对其进行了更多的投资,并且他们更容易接受这些优化贡献。另一方面,CPython 核心开发人员历来不太愿意接受仅以牺牲代码库的长期可维护性为代价来提高性能的贡献。如果它使代码难以阅读,如果它使新贡献者难以加入项目,特别是如果贡献者没有明显的长期承诺来维护代码,那么改进就不太可能被接受。
  2. 人们期望能够编辑Python程序并且它可以立即开始运行。人们期望用Python编写的程序具有快速的启动时间,即使该程序最近被编辑过。当然,一旦程序被编译,编译语言通常启动速度更快(尽管,IME,Java 通常不是这种情况),但提前(AOT)编译语言可能需要很长时间进行全局优化,因为它们不进行全局优化。不需要担心编辑-编译-运行循环的速度。
  3. 在 python 中,几乎所有东西在运行时都是可变的。您可以在定义模块、类和函数对象后对其进行修改;改变它们也很常见,因为 Python 可以很容易地做到这一点。Java 更容易优化,因为 Java 中有更多本质上不可变的对象。最重要的是,如果开发人员有一种方法可以冻结模块及其类,并固定它们的导入名称,那么这将带来很多优化机会。


Python 在某些操作上可以非常快,例如对列表进行排序,因为列表的工作方式意味着运行时已经将元素放入易于重新排序的结构中,而且它使用了一种称为Timsort 的高度优化的排序算法。它不像 C 甚至 Java 中的数组。

汽车比喻
想象一下,你正试图在两种汽车之间做出选择。一种就像 Java - 它经过多年的微调,引擎性能经过优化,引擎盖下的马力强劲。这是因为 Java 将所有内容都编译成了一种非常接近机器语言的格式。这就好比一辆赛车在上赛道之前就已经经过了调整和调试。

而 Python 就像一辆多功能 SUV。它对用户友好,而且非常灵活--你可以随时改变它的各个部分。但这种灵活性是有代价的。它是动态键入的,这意味着它能在运行过程中找出要处理的数据类型,而不是一开始就知道一切。这对驾驶员(或编码员)来说更方便,但却不具备 "为速度而生 "的因素。

另外,Python 有一个叫做全局解释器锁(Global Interpreter Lock)或 GIL 的东西。这就好比你的 SUV 一次只能使用高速公路上的一条车道,即使在四车道的道路上也是如此。它能确保一切顺利运行,不会发生意外,但它不会赢得任何比赛。

Java 没有这种单通道规则。它可以使用所有通道,充分利用多核处理器的优势--就像有一队人马拉着你的战车,而不是只有一匹马。

但请记住,速度并不代表一切。Python 就像一辆可靠、易于操控的汽车,让你乘坐舒适。它可能不会在与 Java 的飙车比赛中获胜,但它并不总是与速度有关。有时,驾驶的轻松和乘坐的舒适同样重要。