Python的pypy解释器是什么

本文对pypy解释器进行了一些介绍

一、从编程语言的自举说起

自举(bootstrapping)字面理解就是自己能把自己给举起来,换做编程语言来讲就是,自己编译自己。

例如,在只有汇编的年代,自举一款Basic语言编译器的步骤大概如下(举例,不代表真实历史):

  • 用汇编把第一个Basic编译器写出来,我们叫做编译器A
  • 编译器A可用编译Basic语言,但其本身不是Basic写的,所以此时Basic语言还没有实现自举
  • 用Basic语言写出第二版编译器,我们叫做编译器B。用编译器A编译编译器B的源代码
  • 对编译器B进行修改和测试,此时的编译过程都用编译器B进行。如此反复多次直到所有测试用例都通过为止,以保证编译器B的源代码没有bug
  • 最终得到的编译器B是Basic实现的,如果这个编译器未来有更新,也是用Basic实现,如此我们说这个语言完成了自举。

许多编译型的编程语言都实现了自举,例如Basic、C、C++、Go等。仍有很多语言无法实现自举,这些语言以解释型语言为主,例如R和python(如果不算pypy这个解释器的话)。R语言的解释器是C语言实现的,python的主流解释器Cpython也是如此(强调“主流”是因为python语言有好几种不同实现的解释器,除了Cpython之外还有IronPythonJythoncythonpypy等,由于python严格意义上不算一种能够自举的语言,这些解释器可以理解为用不同语言开发的解释器)

python不能自举,但pypy有点自举的雏形,因为这个解释器是python语言实现的。

二、编程语言类型划分

参考: pypy真的能让python比c还快? - 肖恩的文章 - 知乎

(一)静态语言 vs 动态语言

如果在编译时知道变量的类型,则该语言为静态类型。静态类型语言的常见示例包括Java,C,C ++,FORTRAN,Pascal和Scala。在静态类型语言中,一旦使用类型声明了变量,就无法将其分配给其他不同类型的变量,这样做会在编译时引发类型错误。

1
2
3
4
5
# java

int data;
data = 50;
data = “Hello Game_404!”; // causes an compilation error

如果在运行时检查变量的类型,则语言是动态类型的。动态类型语言的常见示例包括JavaScript,Objective-C,PHP,Python,Ruby,Lisp和Tcl。 在动态类型语言中,变量在运行时通过赋值语句绑定到对象,并且可以在程序执行期间将相同的变量绑定到不同类型的对象。

1
2
3
4
5
# python

data = 10;
data = "Hello Game_404!"; // no error caused
data = data + str(10)

一般来说静态语言编译成字节码执行,动态语言使用解释器执行。编译型语言性能更高,但是较难移植到不同的CPU架构体系和操作系统。解释型语言易于移植,性能会比编译语言要差得多。这是频谱的两个极端。

(二)强类型语言 vs 弱类型语言

强类型语言是一种变量被绑定到特定数据类型的语言,如果类型与表达式中的预期不一致,将导致类型错误,比如下面这个:

1
2
3
4
# python

temp = “Hello Game_404!”
temp = temp + 10; # program terminates with below stated error (TypeError: must be str, not int)

python虽然是动态类型语言,但是却是一种强类型语言。

python和我们感觉不一致,背叛了弱类型语言,不像世界上最好的语言 :(

弱类型语言则没有这种绑定,当类型与表达式中的预期不一致时会忽略错误继续执行,甚至对变量类型进行自动转换,例如php语言:

1
2
3
4
5
# php

$temp = “Hello Game_404!”;
$temp = $temp + 10; // no error caused; PHP Warning: A non-numeric value encountered in php shell code on line 1
echo $temp; // output: 10

果然,只有php才是世界上最好的语言 :-D

三、rpython

RPython可以理解为一种Python编程语言规范的子集,同样也是一种python解释器的实现。也就是说RPython仍然遵循Python的语法规范,但限制了一些功能,如一些动态语言特性和一些类型接口,从而提高运行速度。简而言之,RPython就是强调类型安全,而去除动态语言特性的的Python实现。RPython存在的目的就是一个能够将Python语言编译成低级平台代码实现的编译工具。

四、pypy

pypy的官网为: https://www.pypy.org/

另外参考:

pypy使用python的子集rpython实现了解释器,有点像是一种自举。具体来说,pypy由两个部分组成:

  • 用RPython编写的Python解析器。事实上PyPy实现就是基于RPython代码编写的,并且其解释器的可执行版本是通过RPython内置编译组件(翻译工具链)编译成C程序。
  • 一个翻译工具链(Translation Tool Chain)。 可以将RPython代码转换成C代码的编译工具集,当中包含组成PyPy解释器重要的JIT实现、垃圾回收实现的核心组件。

反常识的是rpython的解释器会比c实现的Cpython解释器快? 主要是因为pypy使用了JIT技术。

image.png

Just-In-Time (JIT) Compiler 试图通过对机器码进行一些实际的编译和一些解释来获得两全其美的方法。简而言之,以下是JIT编译为提高性能而采取的步骤:

  1. 标识代码中最常用的组件,例如循环中的函数。
  2. 在运行时将这些零件转换为机器码。
  3. 优化生成的机器码。
  4. 用优化的机器码版本交换以前的实现。

pypy除了速度快外,还有下面一些特点:

  • 内存使用情况比cpython少
  • 垃圾回收策略更优化
  • Stackless 协程模式默认支持,支持高并发(但是新版本的python(≥3.4)也支持协程了!)
  • 兼容性好,高度兼容cpython实现,基本可以无缝切换(pypy官方宣称如此)

然而,目前看来,python的解释器依然以Cpython为主流。既然pypy如此优秀,大家为什么不去用呢?

答案是多方面的。

  • 首先,尽管pypy对python语言本身的支持很好,但对Cpython的一些C语言扩展支持就不太好了。现在的python语言的用户有一大部分是做数据分析和机器学习的,需要用到的各种python模块如numpy、scipy、pytorch等无一例外都用到了C语言实现各自的一些功能(这提升了代码运行速度),而pypy无法使用这些扩展。
  • 其次,pypy本身的那些优点并没有足够优秀,尽管在许多任务上pypy做得比Cpython快,但这种快是建立在Cpython原本就很慢的基础上。当Cpython使用numpy等C语言扩展模块进行加速以后,pypy的优点就显得略微有点不足了。

尽管如此,pypy是有其存在意义的。对于纯python实现的程序(不使用任何第三方模块),用pypy进行解释执行可能会换来运行速度上的提升;此外,pypy也代表着一种python语言自举的可能性。

五、补充:RPython、CPython、Cython的关系

前面说过,pypy是通过Rpython实现的,由于Rpython对动态类型等特性进行了限制,使得python代码有机会进行编译执行。

而Cython是另一种比Cpython更宽松的实现,允许在python中使用一些C语言的特性。Cython所走的路线是兼容CPython的所有特性,尤其Cython要完全兼容CPython的CPext接口,Cython编译的C扩展因此包含Python/C接口的实现细节,由此调用时会带来一定的性能开销。而RPython编译后的C程序则不存在这些代码开销。换句话说,绝大部分情况下,RPython会比Cython性能要高出许多的根本原因。

因此,我们可以用集合对这几种python的实现进行一个关系的展示:

image.png

除了这三种python解释器以外,还有其他几种python解释器,例如C#语言编写的IronPython和pythonnet、Java编写的Jython等,此处不再赘述。