用PyPy加速Python程序

在《Python性能优化指南–让你的Python代码快x3倍的秘诀》中有提到,我们可以用更好的Python运行环境或运行时优化来提升Python的速度,其中最成熟、使用最简单的当属PyPy。用PyPy,可以在不改变源代码的情况下,获得平均3-4倍的性能提升。本文将带大家学习如何用PyPy加速Python程序。

文章目录

  • 什么是PyPy
  • 安装PyPy
    • 用包管理器安装
    • 用预编译好的安装包安装
  • 使用PyPy
  • PyPy的构建
  • PyPy性能优化关键技术
    • JIT(Just-In-Time)编译
    • 垃圾回收
  • PyPy的局限
    • C语言扩展支持不佳
    • 不适合短时间运行场景
    • 无法实现提前编译
  • 总结

什么是PyPy

Python解释器有多种实现,比如

  • C语言实现的CPython;
  • Java实现的Jython ;
  • .NET实现的IronPython ;
  • Python实现的PyPy;

CPython是Python最原始的实现,也是迄今为止最流行和维护最多的实现。当我们说Python时,通常指的是CPython。你现在使用的Python大概率就是CPython。

然而,因为Python是高级解释型语言,CPython有其局限性,无法获得速度上的优势。而这恰恰是PyPy发挥优势的地方。由于PyPy遵循Python语言规范,不需要更改代码库,并且可以通过JIT显著提高速度。

这里您可能会问,既然CPython和PyPy实现的是相同的语法,为什么CPython不可以实现PyPy的强大功能?原因是,实现这些功能需要对源代码进行巨大的更改,这将是一项庞大到近乎不可能完成的任务。

作为Python的使用者(非维护者),这里我们不过多的深入到Python的底层和源代码,我们先看一下PyPy怎么用。

安装PyPy

有2种方法可以安装PyPy:

用包管理器安装

很多操作系统和Linux发行版都带有PyPy包。

如果是Ubuntu,可以用apt安装

$ sudo add-apt-repository ppa:pypy/ppa
$ sudo apt update
$ sudo apt install pypy3

如果是CentOS,可以用yum安装,以CentOS 7.4为例

$ rpm -ivh http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm
$ yum -y install pypy-libs pypy pypy-devel

如果是MacOS,可以通过 Homebrew安装

$ brew install pypy3

用预编译好的安装包安装

预编译好的安装包可以通过这里下载,你可以根据你的环境下载对应的安装包。

如果是Windows系统,下载好安装包后解压安装就可以了。

如果是Linux,直接解压安装包,

$ tar xf pypy3.9-v7.3.9-linux64.tar.bz2
$ ./pypy3.9-v7.3.9-linux64/bin/pypy3
Python 3.9.10 (?, Mar 16 2022, 16:03:21)
[PyPy 7.3.9 with GCC 4.2.1]
Type "help", "copyright", "credits" or "license" for more information.

然后在/usr/local/bin中创建一个pypy的链接。

使用PyPy

PyPy安装好后,我们实际测试一下PyPy对Python程序运行速度的提升。我们的测试脚本test.py如下:

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

上面的代码用了双重循环,每重循环从[1, 9999],然后输出累加结果。我们用cProfile看一下执行效率:

用CPython执行

$ python -m cProfile test.py

用PyPy执行

$ pypy3 -m cProfile test.py

通过上面的简单测试我们能看到,CPython虽然运行时栈很简单,只有4个函数调用,但是总共用时15.509秒;而PyPy调用了303个函数,但总共用时5.618秒。PyPy比CPython快3倍!

更多性能对比可以参看PyPy的Speed Center,这里有大量性能对比数据。

这里需要注意的是,PyPy对代码速度的提升不是稳定一成不变的,而是跟代码的功能有密切关系。某些情况下PyPy的速度反而更慢。但是平均来讲PyPy比CPython快4.3倍

PyPy的构建

我们习惯说PyPy是用Python写的,于是很多人以为PyPy是Python语言自举的产物。这个观点是不对的。这里我专门为大家纠正一下这个错误观念。

PyPy在建立之初有两个目标:

  1. 创建一个能够为动态语言生成解析器的动态语言框架
  2. 其次才是用这个框架实现一个Python解析器。

第二点目标的产物就是PyPy,上一节我们已经体验了PyPy的性能威力。第一点目标中的动态语言框架叫RPython,PyPy就是用RPython写的。

开篇提到的PyPy是用Python写的其实不准确,这只是习惯性的简略说法。我们习惯说PyPy是用Python写的是因为RPython跟Python有着相同的语法,PyPy实际是用RPython写的。为了解除大家这里的迷惑,这里简单介绍一下PyPy是怎样开发出来的:

  1. 首先用RPython写PyPy的源代码;
  2. 然后用 RPython翻译工具翻译转换源代码,让代码更高效。这个过程会将源代码编译成机器码。这就是为什么Windows, Mac,Linux要分别下载对应操作系统的安装包;
  3. 生成二进制可执行文件,这就是pypy3。

为了避免混淆,这里梳理一下几个易混淆的名词:

  • PyPy指的Python解释执行器,是Python的一个具体实现。
  • RPython是一门语言,语法跟Python一样。
  • RPython翻译工具是生成动态语言解释执行器的框架。

PyPy是用RPython写的,用RPython翻译工具生成的一个Python实现。

PyPy性能优化关键技术

JIT(Just-In-Time)编译

你可能之前听说过JIT技术,他是一种介于编译型语言(比如C)和解释型语言(比如Python)之间的中间语言技术。编译型语言编译器会预先将源代码编译成二进制机器码,CPU可以直接执行,因此执行速度快,但缺点是可以执行差;而解释型语言可移植性好,但执行速度慢,需要解释器一行一行解释执行。

编译型语言和解释型语言是天平的两端,一个的优势就是另外一个的劣势。有没有办法能将兼得二者的优势呢?于是就出现了中间解决方案。Python就结合了编译型语言和解释型语言,先将源代码编译成中间字节码,然后再由CPython解释执行。这样即能兼顾可以执行,同时性能比纯解释型语言好很多。

然而,即便这样,中间字节码的执行效率依然无法与编译成机器码同日而语。其中一个原因是编译器可以对编译的机器码做很多优化,而自己码能做的就很有限。

此时JIT技术就应运而生,它试图整合机器码和解释器二者的优势。简而言之,JIT通过如下步骤提升程序性能:

  1. 识别代码中最常用的部分,例如循环中的函数;
  2. 在运行时将这些部分转换为机器码;
  3. 优化生成的机器码;
  4. 用优化过的机器码替换原来的实现。

下图是编译型语言、解释型语言和JIT编译的开发、执行流程对比图

我们前面给出的示例程序中使用了双重循环,PyPy检测到相同的操作正在反复执行,于是将其编译成机器代码并加以优化,然后用优化后的机器码替换原来的Python实现,从而后面的执行全部执行的是速度更快的机器码。这就是为什么用PyPy执行速度有如此大的提高。

垃圾回收

我们都知道,在程序中创建变量、方法以及一切对象都需要分配内存。C和C++需要我们手动管理内存的分配和释放,而Python提供了垃圾回收(GC)机制自动帮我们管理内存。

垃圾回收有多种实现方法和策略。

CPython用的技术叫**“引用计数”。简单的说就是,当对象被引用时引用计数增加,对象被取消引用时引用计数减少。当引用计数为0时,CPython会自动调用该对象的内存释放函数释放内存。这是一种简单有效的技术,很多编程语言(如Objective-C,Swift, Rust中的Rc/Arc)都采用这种方法管理内存。但它存在一个缺点,那就是“全局暂停”** 。

想象一下,当一个大型对象树的根节点引用计数变为0时,此时需要释放树上所有相关对象。此时程序需要大量执行内存释放函数,无法执行任何其他功能,从而导致程序像暂停了一样。这个现象就叫“全局暂停”。

除了大量释放内存会带来全局暂停外,**“循环引用”**会带来更严重的全局暂停。

循环引用顾名思义就是A引用了B,B又引用了A,他们构成了一个环。此时要释放A需要解除B对它的引用,而要释放B需要解除A对它的引用,这样就构成了无限循环。

#mermaid-svg-4gLt68SJ7eCuFjqU {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU .error-icon{fill:#552222;}#mermaid-svg-4gLt68SJ7eCuFjqU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4gLt68SJ7eCuFjqU .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-4gLt68SJ7eCuFjqU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4gLt68SJ7eCuFjqU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4gLt68SJ7eCuFjqU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4gLt68SJ7eCuFjqU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4gLt68SJ7eCuFjqU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4gLt68SJ7eCuFjqU .marker.cross{stroke:#333333;}#mermaid-svg-4gLt68SJ7eCuFjqU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4gLt68SJ7eCuFjqU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU .cluster-label text{fill:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU .cluster-label span{color:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU .label text,#mermaid-svg-4gLt68SJ7eCuFjqU span{fill:#333;color:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU .node rect,#mermaid-svg-4gLt68SJ7eCuFjqU .node circle,#mermaid-svg-4gLt68SJ7eCuFjqU .node ellipse,#mermaid-svg-4gLt68SJ7eCuFjqU .node polygon,#mermaid-svg-4gLt68SJ7eCuFjqU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4gLt68SJ7eCuFjqU .node .label{text-align:center;}#mermaid-svg-4gLt68SJ7eCuFjqU .node.clickable{cursor:pointer;}#mermaid-svg-4gLt68SJ7eCuFjqU .arrowheadPath{fill:#333333;}#mermaid-svg-4gLt68SJ7eCuFjqU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4gLt68SJ7eCuFjqU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4gLt68SJ7eCuFjqU .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-4gLt68SJ7eCuFjqU .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-4gLt68SJ7eCuFjqU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4gLt68SJ7eCuFjqU .cluster text{fill:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU .cluster span{color:#333;}#mermaid-svg-4gLt68SJ7eCuFjqU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4gLt68SJ7eCuFjqU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

A
B

为了解决循环引用问题,CPython引入了循环垃圾回收器。循环垃圾回收器会从已知的根开始遍历内存中的所有对象,然后识别所有可到达的对象,并释放无法到达的对象,因为这些不可达的对象已经不在树上,说明他们不会再被用到。这样就解决了循环引用的问题。然而,当内存中存在大量对象时,循环垃圾回收会产生更明显的暂停。

而PyPy在内存管理上没有采用“引用计数”技术,而是采用了“循环查找器”技术。循环查找器会周期性地从根开始遍历活动对象,不依赖对象的引用计数,从而降低了内存管理上的花销。

此外,PyPy没有像CPython那样在一个主任务中完成所有工作,而是将工作拆分为数量可变的子任务,然后运行每个子任务,直到所有子任务都运行完成。在每个子任务完成后都会执行一次垃圾回收,这样做可以避免内存中出现大型对象树,从而减少垃圾回收带来得全局暂停。

PyPy的局限

PyPy性能这么好为什么不像CPython那么普及?这是因为PyPy存在一些局限。这些局限导致有些任务场景下PyPy并不适用。

C语言扩展支持不佳

PyPy非常适合运行纯Python程序,如果程序中用到了C语言扩展,PyPy的运行速度比CPython慢很多。这是因为PyPy对C语言扩展的支持不完整,也缺乏相应的优化。并且,对C语言扩展的支持需要PyPy执行引用计数,这进一步减缓了PyPy的速度。

对于这一点,PyPy团队的建议是移除C语言扩展,用纯Python代码来实现,这样就可以使用JIT优化。否则,就乖乖用CPython。

尽管如此,PyPy核心团队正在研究C语言扩展的移植。一些包已经移植到PyPy,工作速度一样很快。

不适合短时间运行场景

举个例子,假如你要去家旁边的超时购物,你会步行去还是开车去?开车速度肯定比不行速度快,但开车要去停车场、发动车子,到了目的地要找停车位停车;回程还要缴费、重新将车停好。这些开车带来的附加工作所消耗的时间可能比步行过去还要多。

PyPy也是如此。当我们用PyPy运行Python脚本时,PyPy会做大量工作让程序能跑得快一点。但如果程序运行时间很短,那么PyPy为速度优化而做出的开销反而会让它的总运行时间比CPython更长。我们可以用之前的例子,将10000改为100,在对比一下CPython和PyPy的运行时间:

用CPython执行

用PyPy执行

从上面的输出可以看出,CPython用时0.001秒,而PyPy用时0.003秒。这次变成CPython比PyPy快三倍!

但如果我们的程序需要长时间运行,那么PyPy为速度优化做出的开销就很值得,能换来更短的总运行时间。

无法实现提前编译

如前面所讲,PyPy不是一个完全编译的Python实现。它会在运行时编译Python代码,但不是Python代码的编译器。由于Python固有的动态性,不可能将Python编译成独立的二进制文件并重用它。

这就导致PyPy作为一种运行时解释器,他的性能比纯解释器(比如CPython)要快,但跟编译型语言(比如C语言)比还是慢很多。

总结

PyPy是CPython的一个快速而强大的替代品。用PyPy,你可以在不改动源代码的情况下获得约3倍左右的速度提升。然而,PyPy不是银弹,它有其局限性,实际项目中用PyPy是否能获得实质性的提升要看具体代码功能的情况,建议可以用PyPy做一下测试,可能会收获显著的性能提升。

用PyPy加速Python程序相关推荐

  1. python找不到reshape_如何加速Python程序

    这次就说一种简单的方式来加速python计算速度的方法,就是使用numba库来进行,numba库可以使用JIT技术即时编译,达到高性能,另外也可以使用cuda GPU的计算能力来加速,对python来 ...

  2. linux下载python numba,安装numba和使用numba加速python程序

    这是从其他博客粘贴过来,备份的.原文在:http://www.cnblogs.com/freeweb/p/6652607.html 使用Cython来加速python程序的运行速度,但是相对来说程序改 ...

  3. matlab简单程序实例_【简单实例】如何使用C++加速python程序

    C++党最鄙视Python的两个原因: 1,计算速度太慢: 2,动态语言特性. 对于第2个原因,这需要python程序员好好规范自己的编写习惯,加深对语言和系统的理解.其实作为动态语言的python依 ...

  4. 使用PyPy加快Python程序执行速度

    Python可以说是近年来比较火的编程语言之一了,Python的优点就不多说了,写过Python的同学应该都有所体验,Python独有的一些特性,写起来真实太舒服了,但是老实讲,Python也没有像各 ...

  5. pypy加速python

    Pypy:是用Python自身实现的解释器.针对CPython的缺点进行了各方面的改良,性能得到很大的提升.最重要的一点就是Pypy集成了JIT.但是,Pypy无法支持官方的C/Python API, ...

  6. python speed为0但是速度过快_通过并发加速你的 python 程序

    本文翻译至 realpython 上题为 Speed Up Your Python Program With Concurrency 的教程,教程对怎么利用并发加速 python 程序分析非常全面到位 ...

  7. python程序写完后点哪个运行快_让 Python 代码运行更快的最佳方式!

    PyPy与CPython PyPy是Python解释器CPython的直接替代品.CPython将Python编译为中间字节码然后由虚拟机解释,而PyPy使用实时(JIT)编译将Python代码转换为 ...

  8. python语言是编译执行_加速Python运行的工具

    加速Python运行的工具应用 如果你需要让你的Python程序加速运行,在不同的层次有一些不同的解决方案: 重写 你的 Python 代码, 通过 并行化parallelizing 和 优化opti ...

  9. Python程序员常犯的十个错误

    不管是在学习还是工作过程中,人都会犯错.虽然Python的语法简单.灵活,但也一样存在一些不小的坑,一不小心,不管是初学者还是资深Python程序员都有可能会栽跟头. 常见错误1:错误地将表达式作为函 ...

最新文章

  1. 利用https访问站点(基于linux系统)
  2. 力软 框架 转 mysql_快速web开发框架——learun framework
  3. 开发商微信选房后不退认筹金_【震惊】胶州恒大文化旅游城项目爆丑闻!2000余人缴纳认筹金后竟有1800余户因对房子不满意提出退款...
  4. 干货收藏 | 如何优化前端性能?
  5. mysql coalesce函数用法_MySQL coalesce函数用法说明(转)
  6. centos 搭建php运行环境
  7. UVA 1611 Crane
  8. 基于ssm在线视频教育网站源码
  9. 如何规避rm-rf导致的服务器删除问题
  10. kuangbin 简单搜索 A 棋盘问题
  11. 生活之游戏中的心理学
  12. 微波雷达感应模块,智能马桶传感方案,智能化生活
  13. 7步学会在Windows下上架iOS APP流程
  14. DOTA数据集 | 数据前后处理操作系列
  15. Elasticsearch 实现类似SQL语句中like %关键字% 的单纯模糊查询(不进行分词)
  16. 滤镜怎么调好看?分享给图片调色的教程
  17. AWS Academy 学习平台建立 Learner Lab - 教师
  18. Android 11.0 ActivityManagerService的启动流程
  19. 移动web——微金所实战项目
  20. 图书借阅管理系统的设计与实现(Java+SSH+MySQL)

热门文章

  1. 三菱数据移位指令_三菱移位指令的应用
  2. 分辨率、Dpi 的关系
  3. spark集群,kafka集群和zookeeper的docker集群快速搭建
  4. 腾讯大佬总结的人脸表情识别技术
  5. Flink Standalone 集群部署
  6. 10个最好的 JavaScript 动画库【值得收藏】
  7. 国产绘画AI杀疯了!画的小姐姐比真人还好看
  8. linux ftp服务器匿名登录不能上传解决方法
  9. scrollLeft
  10. 论文与应用简介:用深度学习设计新的Pokémon角色?(像素风格)