用PyPy加速Python程序

news2024/10/5 19:27:32

用PyPy加速Python程序

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

用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

用预编译好的安装包安装

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

PyPy安装包下载

如果是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 + j

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

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

用CPython执行

$ python -m cProfile test.py

CPython运行时间

用PyPy执行

$ pypy3 -m cProfile test.py

PyPy运行时间

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

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

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

PyPy的构建

人们经常会说_PyPy是用Python写的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编译的开发、执行流程对比图

编译型、解释型、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对它的引用,这样就构成了无限循环。

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执行

CPython运行时间

用PyPy执行

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做一下测试,可能会收获显著的性能提升。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28506.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二叉树相关OJ - C++

文章目录:根据二叉树创建字符串二叉树的层序遍历二叉树的最近公共祖先二叉搜索树与双向链表从前序与中序遍历序列构造二叉树从中序与后序遍历序列构造二叉树二叉树的前序遍历(非递归)二叉树的中序遍历(非递归)二叉树的…

【LeetCode与《代码随想录》】数组篇:做题笔记与总结-Java版

代码随想录地址 是学习过程中的笔记!图来自代码随想录。 文章目录理论题目704. 二分查找35. 搜索插入位置34. 在排序数组中查找元素的第一个和最后一个位置69. x 的平方根367.有效的完全平方数理论 数组是存放在连续内存空间上的相同类型数据的集合。 数组下标都是…

[附源码]java毕业设计新能源汽车租赁管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

基于armv8的kvm实现分析(一)虚拟化介绍

本文基于以下软硬件假定: 架构:AARCH64 内核版本:5.14.0-rc5 1 什么是虚拟化 虚拟化就是把一台物理计算机虚拟成多台逻辑计算机,每台逻辑计算机里面可以运行不同操作系统,而相互之间不受影响,其典型架构…

面试了个 985 毕业的同学,回答“性能调优”题时表情令我毕生难忘

又逢“金九银十”,年轻的毕业生们满怀希望与忐忑,去寻找、竞争一个工作机会。已经在职的开发同学,也想通过社会招聘或者内推的时机争取到更好的待遇、更大的平台。 然而,面试人群众多,技术市场却相对冷淡,面…

JavaIO流:概述

在接触 IO 流前,无论是 变量的声明、数组的创建,又或者是复杂的并发设计还是 Jvm 的性能调优,我们更多的还是和内存打交道。但我们知道计算机组成包括运算器,控制器,存储器,输入设备,输出设备。…

springcloud4:服务注册中心Eureka

直接调用即可,为什么用Eureka什么是服务治理? 多个服务调用,需要有依赖中心管理什么是服务注册? 有一个注册中心,当服务器启动时,会把自己的信息注册到注册中心上什么是服务发现? Client通过注册…

electron打包ffi-napi报错 npm ERR! gyp reason: read ECONNRESET

问题描述 这个问题用了我两天的时间,所以记录一下。 我们项目是使用electronvue,做支付功能的时候需要使用到ffi-napi依赖包。 最后打包的时候ffi-napi报错了,在package.json中去掉ffi-napi就可以打包,但是打包运行后提示缺少ff…

re:Invent 2022,探秘亚马逊云科技的重量级计算创新——Nitro

诞生于16年前的亚马逊云科技,开创了一个全新的云计算领域。秉持着创新与探索精神,自2012年开始,在每年一度的re:Invent全球大会上,亚马逊云科技都会发布最新的云计算技术。对IT产业演进产生了革命性的影响,Nitro系统就…

Java笔记(工厂模式、动态代理、XML)

一、工厂模式 软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的复用性。 什么…

代码随想录算法训练营第四十三天| LeetCode1049. 最后一块石头的重量 II、LeetCode494. 目标和、LeetCode474. 一和零

一、LeetCode1049. 最后一块石头的重量 II 1:题目描述(1049. 最后一块石头的重量 II) 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出任意两块石头,然后将…

让你快速高效的掌握linux内核编译过程

Linux内核编译 一、linux内核的配置与编译: 1.配置内核 1)导入默认配置: make xxxx_defconfig 注1:xxxx表示内核支持的芯片的名称 比如make exynos_defconfig 注2:内核源码中对每个支持的芯片都有默认的配置,默认配置很…

【博学谷学习记录】超强总结,用心分享|架构师-RabbitMQ消息可靠性保障

文章目录一、生产者保证1.1 失败通知1.2 发送方确认1.3 Broker丢失消息二、消费方消息可靠性2.1 消费者手动确认消息依靠三个对象:生产者、消费者、broker一、生产者保证 生产者发送消息到broker时,要保证消息的可靠性,主要的方案有&#xf…

5.28 综合案例2.0-简易起夜灯

HaaS506 - 简易起夜灯简介准备硬件连接图功能实现1.继电器使用说明2. 5.8G雷达感应传感器模块说明3.简易代码3.1测试log简介 案例为了解决晚上起床找不到灯的问题。当你从床上起来时,雷达感应传感器检测到你的活动后自动打开电灯。省去了寻找电灯开关的麻烦。 准备…

java学习笔记 day07-Java基础-综合练习

练习一:飞机票 需求: ​ 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。 ​ 按照如下规则计算机票价格:旺季(5-10月)头等舱9折,经济舱8.5折,淡季(11月到来…

表白墙(web版)

文章目录前言一、需求分析1.表白墙页面设计2.表白墙功能二、实现1.客户端2.服务器端3.连接数据库前言 前面前端部分写过一个表白墙页面,但是它不能存储提交信息,为了能够让它在提交信息后可以保存其信息,页面刷新后信息依然存在,…

itk配准整理(1)

示例地址: itk\ITK\Examples\RegistrationITKv4\ImageRegistration7.cxx 说明:itk二维图像的配准:平移旋转缩放 效果图: 运行结果: 52 53.6213 [0.8333298229719548, -0.17450270771316403, -12.806452097490313, -1…

在Win10中使用YAMAHA S-YXG50软波表

曾经非常经典的一款软波表YAMAHA S-YXG50我个人非常的喜欢。在XP系统的时代,是我必装的软件,用来听一些MIDI音质和效果很好。而如今玩MIDI的人越来越少了,软波表的时代也被人渐渐的遗忘了。 如今想要怀旧的话,只能够找一台老电脑…

LINUX的XEN和KVM到底区别在什么地方?

本文调研的是 Completely Fair Scheduler 算法, 它当前是 Linux 中 SCHED_NORMAL(非实时任务) 一类 task 的默认调度器. 实际上, 运行在 Guest OS 中的应用程序线程还受到 Guest OS 的调度, 分时运行在 vCPU 上, 但这不在本文调研范围内. 本文仅调研 vCPU 被如何调度到 pCPU 上…

WebDAV之葫芦儿·派盘+无聊笔记

无聊笔记 支持webdav方式连接葫芦儿派盘。 无聊笔记是一款深受用户认可的系统工具,随时都可以进行软件的操作,也可以在线进行笔记的记录,系统会进行智能的保存,满足了用户日常中的需求,可以把软件作为备忘录,笔记本,摘抄录等。 无聊笔记是一个功能强大的记录工具,您…