我的理解
C++被编译成机器码并被执行。
Python被编译成字节码
然后执行此字节码
这个执行步骤需要什么,Cpython 和 PyPy 有什么不同?
性能差异体现在哪里?Python 是动态类型的这一事实在性能方面有何影响?
谢谢!
我的理解
C++被编译成机器码并被执行。
Python被编译成字节码
然后执行此字节码
这个执行步骤需要什么,Cpython 和 PyPy 有什么不同?
性能差异体现在哪里?Python 是动态类型的这一事实在性能方面有何影响?
谢谢!
C、C++等静态编译语言被编译成本机机器码,这意味着计算机的CPU可以直接执行它们。它编译成的代码是难以理解的二进制数据,但您可以想象这样的 C 代码片段:
int x = 10;
int y = x * 2;
将被编译成一系列二进制指令,其含义如下:
store 10 into memory address 200
multiply the contents of memory address 200 by 2, treating them as integers
store the result of the last instruction into memory address 300
编译器已将内存地址分配给变量x
并y
出现在代码中。请注意,实际的机器代码比这更复杂,并且显然被编码为短二进制代码字,而不是英文短语。但这是非常基本的想法。需要特别注意的一点是,编译器知道使用整数乘法,因为它知道x
并且y
是整数。CPU 本身对内存地址 200 的内容的含义一无所知,它只知道位,并且可以被告知以各种方式对它们进行混洗,其中一种是整数乘法。
现在 Python 被编译为字节码。当我们谈论这些问题时,这实际上并不意味着非常有趣。Python 字节码与机器码不同,它不编码机器可以直接执行的低级操作。实际上,它基本上只是对您在 Python 源代码中编写的相同 Python 级别的操作进行编码,而 CPU 根本无法使用 Python 字节码做任何事情。Python 解释器是一个程序,负责执行以 Python 字节码编码的指令。字节码编译所做的只是让解释器对一种更容易、更快速地操作的代码形式进行操作;它不必进行直接理解 Python 源代码所需的所有字符串处理。
所以这就是动态类型和性能差异的来源。看到的 C++ 编译器x * 2
知道它可以将它编译为 CPU 的单个整数乘法指令,因为它提前知道所涉及的所有内容的类型。
一个看到的 Python 解释器x * 2
必须经过许多步骤来查看是否x
有任何支持乘法的内置类型,或者它是否是实现自定义乘法的类,或者它是否是不实现乘法但继承的类从其他的东西,或者它是否应该创建一个异常。如果x
是一个整数,则有步骤从表示 Python 整数的数据结构中获取机器级值x
,然后是 1 条机器级指令实际让 CPU 进行整数乘法,然后还有更多指令要包装结果备份到 Python 整数数据结构中。
所有这些代码都是许多已编译的机器代码指令(通常;对于在 CPython 之上运行的 PyPy,它们是 Python 字节码指令!);Python解释器本身的编译代码。您可能认为 Python 的字节码编译器可以提前确定要采用的路径并将 Python 源代码转换为那些机器指令,但它不能,因为 Python 是动态类型的;x
可能是第一次执行该行代码时的整数,然后是下一次执行的字符串,以及之后时间的列表,甚至可能有一天是类实例。所以所有这些逻辑都必须每次都完成,因为 Python 无法提前知道它需要什么。因此,即使您编写了一个将 Python 源代码编译为本机机器代码的程序,大多数情况下它也必须发出与 Python 解释器基本相同的机器代码。
作为一个非常简单的概述,这涵盖了您的大部分问题。您还询问了 PyPy,但没有真正提供您感兴趣的任何细节。我认为这是“为什么 PyPy 比 CPython 快(某些时候)?” 基本上 PyPy 有一个 JIT 编译器,它有点像 C++ 编译器,只是它在程序执行期间编译代码。这可以(有时)解决 Python 无法知道x
是整数、浮点数、列表还是其他东西的问题。在任何一次执行一点代码,x
只是一件事。而在大多数 Python 代码中,x
永远只是一件事,或者偶尔是几件事之一。因此,通过在运行时编译代码(在等待查看哪些是真正经常执行的代码之后),PyPy'x * 2
成单个整数乘法机器代码指令。如果我们以整数形式执行该行代码x
数百万次,这可能会大大提高性能。但是下一次仍然可能是x
字符串,因此 JIT 必须包含一些回退逻辑,以便它仍然可以处理 Python 允许的所有可能性。但它可以通过等待查看实际经常使用的众多可能性中的哪一个来提高速度,然后针对这些可能性进行优化。JIT 甚至可以进行 C++ 编译器无法进行的一些优化,因为它可以等待查看运行时发生了什么,而 C++ 必须发出可以在运行时发生的任何事情的代码(但它可以根据类型做出假设,永远不会改变)。