用 PyPy 让你的 Python 代码运行得更快!

共 5754字,需浏览 12分钟

 ·

2020-11-17 04:12

Python是开发人员中最常用的编程语言之一,但它有一定的局限性。例如,对于某些应用程序而言,它的运行速度可能比其它语言低100倍。这就是为什么当Python的运行速度成为用户瓶颈后,许多公司会用另一种语言重写他们的应用程序。但是有没有一种方法既可以保持Python的特性又能提高速度呢?它就是PyPy
PyPy是一种非常兼容的Python解释器,它是CPython2.7、3.6和即将推出的3.7的一种值得替代的方法。在安装和运行应用程序时使用它,可以显著提高速度。速度提高多少取决于你运行的应用程序。
在本教程中,您将学习:
  • 如何使用PyPy安装和运行代码
  • PyPy与CPython在速度方面的比较
  • PyPy的功能及其如何使Python代码更快地运行
  • 本教程中的示例使用 Python 3.6 ,因为它是PyPy兼容的最新 Python 版本。
PyPy 简介
Python解释器可以用多种语言来实现,如CPython(用C编写)、Jython(用Java编写)、Iron Python(用.NET编写)和PyPy(用Python编写)。
CPython是Python解释器的最初实现,也是迄今为止使用最广和最多维护的。当我们从Python官方网站下载并安装好Python 3.x后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。
但是,由于CPython是一种高级的解释语言,因此它有一定的局限性,并且在速度方面没有任何优势。这就是PyPy可以起作用的地方。由于它符合Python语言规范,因此Py Py不需要对代码库进行任何更改,并且可以通过下面的功能显著提高速度。
现在,您可能想知道,如果CPython使用相同的语法,为什么它不实现Py Py的强大功能。原因是,实施这些功能需要对源代码进行巨大的更改,这将是一项非常繁琐的工作。
我们来粗略看一下如何在实际操作中使用PyPy。
安装
您的操作系统可能已提供PyPy软件包。例如,在Mac OS上,您可以在Homebrew的帮助下安装它:
$ brew install pypy3
或者您也可以下载与操作系统匹配的二进制文件。完成下载后,只需打开tarball或ZIP文件即可。然后,您可以执行以下操作:
$ tar xf pypy3.6-v7.3.1-osx64.tar.bz2
$ ./pypy3.6-v7.3.1-osx64/bin/pypy3
Python 3.6.9 (?, Jul 19 2020, 21:37:06)
[PyPy 7.3.1 with GCC 4.2.1]
Type "help", "copyright", "credits" or "license" for more information.
您需要在上述文件夹地址执行该命令。有关完整的说明,请参阅安装文档。
运行 PyPy
您现在已经安装了Py Py,并且即将运行它!为此,请创建一个名为script.py的Python文件,并将以下代码放入其中:
total = 0
for i in range(110000):
    for j in range(110000):
        total += i + j

print(f"The result is {total}")
在两个嵌套的for循环中,将1到9,999之间的数字相加,并打印结果。
查看运行此脚本需要多长时间:
import time

start_time = time.time()

total = 0
for i in range(1, 10000):
    for j in range(1, 10000):
        total += i + j

print(f"The result is {total}")

end_time = time.time()
print(f"It took {end_time-start_time:.2f} seconds to compute")
该代码现在执行以下操作:
  • 第3行将当前时间保存到变量start_time
  • 第5至8行运行循环。
  • 第10行打印结果。
  • 第12行将当前时间保存为end_time
  • 第13行打印开始时间和结束时间之间的差值,以显示运行脚本所需的时间。
用Python来运行它。下面是我在Mac Book Pro上的结果:
$ python3.6 script.py
The result is 999800010000
It took 20.66 seconds to compute
现在使用Py Py运行它:
$ pypy3 script.py
The result is 999800010000
It took 0.22 seconds to compute
在这个小实验中,PyPy的速度大约是Python的94倍!
您可以通过浏览 PyPy Speed Center 来查看更多严格的测试。
请记住,PyPy如何影响代码的性能取决于您用代码来做什么。在某些情况下,Py Py实际上较慢,稍后会看到。但是,就几何平均而言,它的速度是Python的4.3倍。
PyPy及其特性
Py Py有两种定义:
1、用于生成动态语言解释器的动态语言框架 2、使用该框架的Python实现
您应该已经意识到了第二个问题。您使用的Python实现是使用称为RPython的动态语言框架编写的,就像CPython是用C编写的,而Jython是用Java编写的一样。
但之前文中不是提到PyPy是用Python编写的吗?嗯,这有点简单。PyPy成为用Python编写的Python解释器(而不是RPython)这么说的原因是RPython使用了与Python相同的语法。
PyPy是怎么来的?需要解释以下几点:
1、它的源代码是用RPython编写。
2、RPython转换工具应用到了代码中,从根本上提高了代码效率,还可以将代码编译为机器代码,这就是Mac,Windows和Linux用户必须下载不同版本的原因。
3、用上述方式生成的二进制可执行文件,就是你运行的Python解释器。
你不需要执行上述所有这些步骤来使用PyPy。因为已经有提供您安装和使用的可执行文件。
此外,由于在框架和实现中使用同一个词非常令人困惑,PyPy背后的团队决定放弃这种双重用法。现在,PyPy仅指Python解释器,而框架被称为RPython转换工具。
接下来,您将了解在什么情况下使用PyPy比Python更好、更快。
Just-In-Time (JIT) 编译器
在了解JIT编译器的内容之前,让我们先回顾一下已编译语言(如C)和解释语言(如JavaScript)的特性。
在编译型语言写的程序执行之前,需要一个专门的编译过程,把源代码编译成机器语言的文件,如exe格式的文件,以后要再运行时,直接使用编译结果即可,如直接运行exe文件。因为只需编译一次,以后运行时不需要编译,所以编译型语言执行效率高。与特定平台相关,一般无法移植到其他平台。如C、C++、Objective等都属于编译型语言。
解释型语言不需要事先编译,其直接将源代码解释成机器码并立即执行,所以只要某一平台提供了相应的解释器即可运行该程序。解释型语言每次运行都需要将源代码解释称机器码并执行,效率较低;只要平台提供相应的解释器,就可以运行源代码,所以可以方便源程序移植。
然后还有一些编程语言,例如Python,它混合了编译和解释。具体来说,Python首先编译为字节码,然后由CPython解释。这使代码的性能优于用纯解释型语言编写的代码,并保持可移植性优势。
但是它的性能仍然远远低于编译型语言。其原因是,编译后的代码可以执行许多优化,而字节码是不可能的。
这就是JIT编译器的来源。它试图通过对机器代码进行一些编译和一些解释来同时获得两种优势。简而言之,以下是JIT编译为提供更快性能所采取的步骤:
1、识别代码中最常用的组件,如循环中的函数。
2、运行时将这些部件转换为机器代码。
3、优化生成的机器代码。
4、用优化的机器代码版本取代之前的实现。
还记得教程开头的两个嵌套循环吗?PyPy检测到重复执行相同操作时,将其编译为机器代码,优化机器代码,然后转换实现。这也是为什么您会看到这样的结果。
垃圾回收机制
无论何时创建变量、函数或任何其他对象,您的计算机都会给它们分配内存。最终,其中一些对象将不再需要。如果不及时清理,计算机可能会耗尽内存并使程序崩溃。
在C和C++等编程语言中,通常必须手动处理此问题。其他编程语言(如Python和Java)会自动为您执行此操作。这被称为自动垃圾回收机制。
CPython使用一种称为引用计数的技术。实质上,每当引用对象时,Python对象的引用计数都会增加,而在取消引用该对象时则递减计数。当引用计数为零时,CPython会自动为该对象调用内存释放函数。这是一种简单有效的技术,但有一个陷阱。
当大型对象树的引用计数变为零时,所有相关对象将被释放。因此,您可能有很长的暂停时间,在此期间您的程序根本无法执行。
此外,还有一个例子,其中引用计数根本不起作用。如下所示:
class A(object):
    pass

a = A()
a.some_property = a
del a
在上面的代码中,定义了新的类,然后,创建一个实例,并将其指定为其自身的属性。最后,删除实例。
此时,实例将不再可访问。但是,引用计数不会从内存中删除实例,因为它具有对自身的引用,因此引用计数不是零。此问题被称为引用循环,无法使用引用计数解决。
这是CPython使用的另一个工具,称为循环垃圾回收器。它从已知根(如类型对象)开始遍历内存中的所有对象。然后,它标识所有可访问的对象,并释放不可访问的对象,因为它们不再存在。这样就解决了引用循环问题。但是,当内存中存在大量对象时,它可能会创建更明显的暂停。
另一方面,PyPy不使用引用计数。相反,它只使用第二种技术,即循环查找器。也就是说,它会定期从根开始遍历活动对象。这使PyPy比CPython具有一些优势,因为它不需要考虑引用计数,从而使内存管理花费的总时间少于CPython。
此外,PyPy将工作拆分为可变数量的部分,并运行每个部分,直到没有剩余部分为止。此方法只在每个次要集合之后添加几毫秒,而不像CPython那样一次添加数百毫秒。
垃圾回收机制非常复杂,并且有许多超出本教程范围的内容。您可以在文档中找到有关PyPy垃圾回收机制的详细信息。
PyPy的局限性
PyPy并非万能,它不是一个适合您所有任务的工具。它甚至可能使应用程序的执行速度比CPython慢得多。这就是为什么您必须记住以下局限性。
它不适用于C扩展
PyPy最适合纯Python应用程序。无论何时使用C扩展模块,它的运行速度都要比在CPython中慢得多。原因是PyPy无法优化C扩展模块,因为它们不受完全支持。此外,PyPy必须模拟代码中的引用计数,使其更慢。
在这种情况下,PyPy团队建议去掉CPython扩展并将其替换为纯Python版本。如果不行的话,则必须使用CPython。
尽管如此,核心团队正在处理C扩展。有些软件包已被移植到PyPy,并且工作速度也同样快。
它只适用于长时间运行的程序
想象一下你想去一家离你家很近的商店。您既可以直接走路前往,也可以开车。
您的车明显比您的脚快得多。但是,请考虑需要您完成的步骤:
1.去你的车库。 
2、开车。 
3、给车预热。 
4、开车去商店。
5、寻找停车位。 
6、在返回途中重复此过程。
开车需要一系列麻烦的步骤,如果你想去的地方就在附近,那就不一定值得了。
现在想想,如果你想去50公里外的邻近城市,会发生什么?开车去那里肯定是值得的,而不是步行去。
虽然速度上的对比并不像上面的类比那样明显,但PyPy和CPython和这个道理一样。
当使用PyPy运行脚本时,它会执行许多操作以使代码运行得更快。如果脚本本身很简单,则实际脚本运行速度会低于CPython。另一方面,如果您有一个长时间运行的脚本,那么可能会带来显著的性能提升。
想亲自感受一下的话,请在CPython和PyPy中运行以下小脚本:
import time

start_time = time.time()

for i in range(100):
    print(i)

end_time = time.time()
print(f"It took {end_time-start_time:.10f} seconds to compute")
当您使用PyPy运行它时,开始时会有一个小延迟,而CPython会立即运行它。在Mac Book Pro上运行它,用CPython需要0.0004873276秒,用PyPy需要0.0019447803秒。
它不执行提前编译
正如您在本教程开头所看到的,PyPy不是一个完全编译型的Python实现。它编译Python代码,但不是Python代码的编译器。由于Python固有的一些特性,导致无法将Python编译为独立的二进制文件并重用它。
Py Py比完全解释型的语言快,但比完全编译的语言(如C)慢。
总结
PyPy是CPython的一种快速且功能强大的替代方案。使用它运行脚本,您可以在不更改代码的情况下大大提高速度。但它也不是万能的,有一些局限性。
在本教程中,您学习了:
  • PyPy是什么?
  • 如何安装PyPy并使用它运行脚本
  • PyPy与CPython在速度方面的比较
  • PyPy的功能及其如何提高程序速
  • 在哪些情况下PyPy会有局限性
如果您的Python脚本需要稍微提高速度,欢迎尝试PyPy!


长按扫码添加“Python小助手” 

一起讨论PyPy!

▼点击成为社区会员   喜欢就点个在看吧

浏览 22
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报