PyPy:速度更快的Python

发布于:2021-01-25 13:49:30

0

533

0

python PyPy Python解释器 CPython

Python是开发人员中最受欢迎的编程语言之一,但是它有一定的局限性。例如,取决于应用程序,它的速度可能是某些低级语言的速度的100倍。这就是为什么一旦Python的速度成为用户的瓶颈,许多公司就会用另一种语言重写其应用程序的原因。但是,如果有一种方法可以保留Python的出色功能并提高其速度呢?那就是PyPy。

PyPy是一个非常兼容的Python解释器,是CPython 2.7、3.6和即将推出的3.7的一个有价值的替代品。通过安装和运行应用程序,可以显着提高速度。您将看到多少改进取决于您正在运行的应用程序。

在本教程中,您将学习:

  • 如何使用PyPy安装和运行代码

  • PyPy在速度方面如何与CPython进行比较

  • PyPy的功能是什么以及它们如何使您的Python代码运行得更快

  • PyPy的局限性是什么

本教程中的示例使用Python 3.6,因为那是PyPy兼容的最新Python版本。

Python和PyPy

Python语言规范用于许多实现中,例如CPython(用C编写),Jython(用Java编写),IronPython(为.NET编写)和PyPy(用Python编写)。

CPython是Python的原始实现,并且是迄今为止最受欢迎和最维护的。当人们提到Python时,他们通常不是指CPython。您现在可能正在使用CPython!

但是,由于CPython是高级解释语言,因此CPython具有一定的局限性,不会为速度赢得任何荣誉。这就是PyPy可以派上用场的地方。由于它遵循Python语言规范,因此PyPy无需更改您的代码库,并且由于您将在下面看到的功能而可以显着提高速度。

现在,您可能想知道为什么CPython如果使用相同的语法,就无法实现PyPy的出色功能。原因是实现这些功能将需要对源代码进行巨大的更改,这将是一项艰巨的任务。

在不深入研究理论的情况下,让我们看看PyPy的作用。

安装

您的操作系统可能已经提供了PyPy软件包。例如,在macOS上,您可以借助Homebrew进行安装:

$ brew install pypy3

如果没有,您可以下载适用于您的操作系统和体系结构的预构建二进制文件。完成下载后,只需解压缩tarball或ZIP文件即可。然后,您可以执行PyPy,而无需将其安装在任何地方:

$ 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

现在,您已经安装了PyPy,可以随时使用它了!为此,创建一个名为的Python文件script.py,并将以下代码放入其中:

total = 0  for i in range(1, 10000):      for j in range(1, 10000):          total += i + j           print(f"The result is {total}")

这是一个脚本,在两个嵌套for循环中,将的数字1加到9,999,然后输出结果。

要查看运行此脚本需要多长时间,请对其进行编辑以添加突出显示的行:

{xunruicms_img_title}

该代码现在执行以下操作:

  • 第3行将当前时间保存到变量中start_time。

  • 第5至8行运行循环。

  • 第10行打印结果。

  • 第12行将当前时间保存到end_time。

  • 第13行显示start_time和之间的差异,end_time以显示运行脚本所需的时间。

尝试使用Python运行它。这是我在2015 MacBook Pro上获得的:

$ python3.6 script.py The result is 999800010000 It took 20.66 seconds to compute

现在使用PyPy运行它:

$ pypy3 script.py The result is 999800010000 It took 0.22 seconds to compute

在这个小的综合基准中,PyPy的速度大约是Python的94倍!

有关更严格的基准测试,您可以查看PyPy Speed Center,开发人员在该中心每晚运行带有不同可执行文件的基准测试。

请记住,PyPy如何影响您的代码性能取决于您的代码在做什么。在某些情况下,PyPy实际上要慢一些,您将在后面看到。但是,就几何平均而言,它的速度是Python的4.3倍。

PyPy及其功能

从历史上看,PyPy提到了两件事:

  1. 一个动态语言框架,用于为动态语言生成解释器

  2. 一个Python实现使用框架

通过安装PyPy并运行一个小脚本,您已经看到了第二个含义。您使用的Python实现是使用称为RPython的动态语言框架编写的,就像CPython是用C编写而Jython是用Java编写一样。

但是您是否没有早些时候告诉过PyPy是用Python编写的?好吧,这有点简化。PyPy之所以被称为Python(而不是RPython)编写的解释器,是因为RPython使用与Python相同的语法。

为了清除所有内容,以下是PyPy的生产方式:

  1. 源代码是用RPython编写的。

  2. 所述RPython翻译工具链被施加到代码,这基本上使得代码更高效。它还将代码编译为机器代码,这就是Mac,Windows和Linux用户必须下载不同版本的原因。

  3. 生成二进制可执行文件。这是您用来运行小脚本的Python解释器。

请记住,您无需完成所有这些步骤即可使用PyPy。该可执行文件已经可供您安装和使用。

而且,由于在框架和实现中使用相同的词非常令人困惑,因此PyPy背后的团队决定摆脱这种双重用法。现在,PyPy仅指Python实现。该框架称为RPython转换工具链。

接下来,您将了解使PyPy在某些情况下比Python更好和更快的功能。

即时(JIT)编译器

在了解什么是JIT编译之前,让我们退后一步,回顾一下诸如C之类的已编译语言和诸如JavaScript之类的解释语言的属性。

编译的编程语言性能更高,但是更难移植到不同的CPU体系结构和操作系统。解释性编程语言更可移植,但是其性能比编译语言差很多。这是频谱的两个极端。

然后是诸如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使用另一种称为循环垃圾收集器的工具的地方。它从type对象的已知根开始遍历内存中的所有对象。然后,它标识所有可到达的对象并释放不可达的对象,因为它们不再存在。这解决了参考周期问题。但是,当内存中有大量对象时,它可能会导致更明显的暂停。

另一方面,PyPy不使用引用计数。相反,它仅使用第二种技术,即循环查找器。也就是说,它定期从根开始遍历活动对象。PyPy相对于CPython具有一些优势,因为它不会打扰引用计数,从而使内存管理上花费的总时间少于CPython。

另外,PyPy并没有像CPython这样的主要任务来完成所有工作,而是将工作分解为可变数量的片段,并逐个运行直到没有剩余为止。这种方法在每个次要集合之后仅增加几毫秒,而不是像CPython那样一次性增加数百毫秒。

垃圾收集很复杂,并且有许多详细信息超出了本教程的范围。您可以在文档中找到有关PyPy垃圾回收的更多信息。

PyPy的局限性

PyPy不是灵丹妙药,并且不一定总是最适合您的任务的工具。它甚至可能使您的应用程序执行速度比CPython慢得多。这就是为什么记住以下限制很重要。

C扩展不能很好地工作

PyPy最适合纯Python应用程序。无论何时使用C扩展模块,它的运行速度都比CPython中慢。原因是PyPy无法完全支持C扩展模块,因此无法对其进行优化。另外,PyPy必须模拟该部分代码的引用计数,从而使其速度更慢。

在这种情况下,PyPy团队建议删除CPython扩展并将其替换为纯Python版本,以便JIT可以看到它并进行优化。如果这不是一个选择,那么您将不得不使用CPython。

话虽如此,核心团队正在致力于C扩展。一些软件包已经被移植到PyPy并以同样快的速度工作。

它仅适用于长期运行的程序

假设您想去一家离您家很近的商店。您可以步行或开车。

您的汽车显然比脚快得多。但是,请考虑需要执行以下操作:

  1. 去你的车库。

  2. 启动你的车。

  3. 稍微加热汽车。

  4. 开车去商店。

  5. 查找停车位。

  6. 在返回途中重复该过程。

开车要涉及很多开销,如果您想去的地方在附近,这并不总是值得的!

现在,想想如果您想去五十英里外的邻近城市会发生什么。开车去那里而不是步行肯定是值得的。

尽管速度的差异并不像上面的类比那么明显,但是PyPy和CPython也是一样。

当您使用PyPy运行脚本时,它会做很多事情来使您的代码运行更快。如果脚本太小,那么开销将导致您的脚本运行速度比CPython中慢。另一方面,如果您的脚本运行时间很长,那么开销会带来可观的性能红利。

要亲自查看,请在CPython和PyPy中运行以下小脚本:

{xunruicms_img_title}

使用PyPy运行时,开始时会有一个小的延迟,而CPython会立即运行它。确切地说,0.0004873276在使用CPython的2015 MacBook Pro上运行它需要几秒钟,而在0.0019447803PyPy上运行它需要几秒钟。

它不进行提前编译

正如您在本教程开始时所看到的那样,PyPy不是完全编译的Python实现。它编译Python代码,但不是Python代码的编译器。由于Python固有的动态性,不可能将Python编译成独立的二进制文件并重新使用它。

PyPy是一个运行时解释程序,它比完全解释的语言要快,但是比完全编译的语言(如C)要慢。

结论

PyPy是CPython的快速而强大的替代品。通过与脚本一起运行脚本,无需对代码进行任何更改即可大大提高速度。但这不是灵丹妙药。它有一些限制,您需要测试程序以查看PyPy是否可以提供帮助。

在本教程中,您学习了:

什么PyPy是

如何安装PyPy并使用它运行脚本

PyPy在速度方面如何与CPython进行比较

PyPy具有什么功能以及如何提高程序速度

PyPy具有哪些限制,可能使其在某些情况下不适合

如果您的Python脚本需要提高速度,请尝试PyPy。根据您的程序,您可能会得到一些明显的速度改进!