alg*_*der 5 c++ python compiler-construction pypy cpython
我的理解
C++被编译成机器代码并执行.
Python被编译成字节码
然后执行该字节码
这个执行步骤需要什么?Cpython和PyPy有什么不同?
性能差异在哪里发挥作用?Python的动态类型在性能方面取得了哪些进展?
谢谢!
C,C++和其他静态编译语言被编译成本机机器代码,这意味着计算机的CPU可以直接执行它们.它编译的代码是难以理解的二进制数据,但你可以想象一下像这样的C代码片段:
int x = 10;
int y = x * 2;
Run Code Online (Sandbox Code Playgroud)
将被编译成一系列二进制指令,意思如下:
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
Run Code Online (Sandbox Code Playgroud)
当编译器已经分配的内存地址的变量x和y中出现的代码.请注意,实际的机器代码比这更复杂,并且显然编码为短二进制代码字,而不是英语短语.但那是非常基本的想法.需要特别注意的是,编译器知道使用整数乘法,因为它知道x并且y是整数.CPU本身对存储器地址200的内容的含义一无所知,它只知道位并且可以被告知以各种方式对它们进行混洗,其中之一是整数乘法.
现在Python被编译为字节代码.当我们谈论这些问题时,这实际上并不是很有意思.与机器代码不同,Python字节代码不编码可由机器直接执行的低级操作.事实上,它基本上只编码你在Python源代码中编写的非常相同的Python级操作,并且CPU根本不能对Python字节代码做任何事情.Python解释器是一个程序,其执行以Python字节代码编码的指令.所有字节码编译都允许解释器在代码的形式上操作,操作更容易和更快; 它不必直接理解Python源代码所需的所有字符串处理.
所以这里有动态类型和性能差异.看到的C++编译器x * 2知道它可以将它编译为CPU的单个整数乘法指令,因为它知道提前涉及的所有类型.
看到的Python解释器x * 2必须经过许多步骤来查看是否x有任何支持乘法的内置类型,或者它是否是实现自定义乘法的类,或者它是否是一个不实现乘法而是继承的类来自其他的东西,或者它是否应该创建一个例外.如果x是一个整数,那么有步骤x从数据结构中获取表示Python整数的机器级值,然后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的JIT 可以(有时)变成x * 2单个整数乘法机器代码指令.如果我们x以整数百万次执行该行代码,这可以大大提升性能.但是下一次仍然可能x是一个字符串,所以JIT必须包含一些回退逻辑,这样它仍然可以处理Python允许的所有可能性.但它可以通过等待查看实际使用的多种可能性中的哪一种,然后优化那些来获得速度.JIT甚至可以对C++编译器不能进行一些优化,因为它可以等待运行时发生的事情,而C++必须发出可以在运行时发生的任何代码(但它可以根据类型做出假设) ,永远不会改变).