Python性能优化指南--让你的Python代码快x3倍的秘诀

news2024/10/6 15:13:51

Python性能优化指南

Python最为人诟病的就是其执行速度。如何让Python程序跑得更快一直是Python核心团队和社区努力的方向。作为Python开发者,我们同样可以采用某些原则和技巧,写出性能更好的Python代码。本文将带大家深入探讨Python程序性能优化方法。

在这里插入图片描述

文章目录

    • 优化原则
    • 优化工具
      • cProfile
      • %%timeit 和 %timeit
      • timeit()方法
      • 第三方工具line_profiler
    • 解决瓶颈
      • 选择合适的算法和数据结构
      • 多用列表推导式
      • 少用`.`操作
      • 善用多重赋值
      • 避免使用全局变量
      • 尽量使用库方法
      • 用join拼接字符串
      • 善用生成器
      • 利用加速工具
      • 用C/C++/Rust实现核心功能
      • 使用最新版本的Python

优化原则

有些优化原则是所有编程语言都适用的,当然对Python也同样适用。这些原则作为程序优化的“心法”,我们每个程序员都要牢记于心,并在日常开发中加以贯彻。

1. 切忌边开发边优化

编写程序时不要去考虑可能的优化,而是集中精力确保代码干净、正确、可读、易懂。如果在写完后发现它太大或太慢,那时再考虑如何优化它。大神高德纳(Donald Knuth)说过一句至理名言:

“过早优化是一切罪恶的根源。(Premature optimization is the root of all evil.)”

这其实也是曾国藩的处事哲学:“物来顺应,未来不迎,当时不杂,既过不恋”

2. 牢记20/80法则

在许多领域,你都可以用20%的努力获得80%的结果(有时可能是10/90法则)。每当您要优化代码时,首先用分析工具找出80%的执行时间花在哪里,这样您就知道应该集中精力优化哪里了。

3. 一定要做优化前后的性能对比

如果不做优化前后的性能比较,我们无法知道优化是否产生了实际效果。如果优化后的代码只比优化前稍快一点,那么请撤消优化并返回优化前版本。因为用牺牲代码的清晰整洁、易读好懂为代价换来的一丁点性能提升不值得。

以上3条优化原则请大家牢记于心,无论今后大家使用何种语言,在做性能优化时都请遵守这3条法则。

优化工具

正如优化原则第二条所讲,我们需要将优化精力放在最耗时的地方。那么怎么找到程序中最耗时的地方呢?此时我们需要用优化工具收集程序运行中的数据,帮我们找到程序瓶颈所在。这个过程称为Profiling。Python中有多个Profiling工具,每个工具各自有其使用场景和重点,下面一一为大家介绍。

cProfile

Python中自带了Profiling工具,名叫cProfile。这也是我推荐大家使用的Profiling工具,因为它功能最强大。它可以注入程序中的每一个方法,收集丰富的数据,包括:

  • ncalls: 方法被调用的次数
  • tottime: 方法执行的总时间(不包含子函数的执行时间)
  • percall: 每次执行花费的平均时间,即tottime除以ncalls的商
  • cumtime: 方法执行的累计时间(包含子函数的执行时间),对递归同样准确有效
  • percall: cumtime除以原始调用次数(不含递归调用)的商
  • filename:lineno(function): 提供每个函数的相应数据

cProfile可以直接在命令行里使用。

$  python -m cProfile main.py

假设我们的main.py实现的是求1000000以内的素数和,代码如下:

import math


def is_prime(n: int) -> bool:
    for i in range(2, math.floor(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True


def main():
    s = 0
    for i in range(2, 1000000):
        if is_prime(i):
            s += i
    print(s)


if __name__ == "__main__": 
    main()

那么执行cProfile后,控制台会输出(部分)如下信息:

在这里插入图片描述

从输出看,整个程序运行花了3.091秒,共有3000064次函数调用,下面列表是每个函数的详细数据。cProfile默认用函数名排序,而我们更关注函数的执行时间,所以通常我们在使用cProfile时会带上-s time,让cProfile按执行时间来排序输出。

$ python -m cProfile -s time .\main.py

按时间排序输出后的信息如下:

在这里插入图片描述

从上面的输出信息可以看到,最耗时的是is_prime函数。如果要优化,is_prime将是我们的优化重点。

%%timeit 和 %timeit

上面介绍的cProfile主要在命令行中使用。但是在数据分析和机器学习中我们经常使用Jupyter作为交互式编程环境。在Jupyter或IPython等交互式编程环境下,cProfile就无法使用了,我们需要用%%timeit%timeit

%%timeit%timeit的区别在于,%%timeit作用于整个代码块,统计整个代码块的执行时间;%timeit作用于语句,统计该行语句的执行时间。还是求质数和的代码,我们看一下在Jupyter中如何获取其运行时间:

在这里插入图片描述

上面代码在代码块开头加入%%timeit, 它会统计整个代码的运行时间。%%timeit会多次运行,取平均运行时长。输出结果如下:

3.87 s ± 151 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

timeit()方法

有时我们可能仅仅是想知道代码中的某个函数或某行语句的执行情况,此时用cProfile显得太重了(cProfile会输出所有函数的执行情况),我们可以import timeit,用timeit()包裹住我们要profile的函数或语句。例如:

import timeit

timeit.timeit('list(itertools.repeat("a", 100))', 'import itertools', number=10000000)

上面的代码会测试list(itertools.repeat("a", 100)) 10000000次,计算平均运行时间。

10.997665435877963

timeit同样可以在命令行中使用。例如:

$ python -m timeit "'-'.join(str(n) for n in range(100))"
20000 loops, best of 5: 10.5 usec per loop

第三方工具line_profiler

上面的介绍的工具都是Python或IPython自带的,提供的功能个更多是函数的运行时间。当我们需要深入地了解程序的执行情况时,上面介绍的3个工具就不够用了。此时我们需要请出代码优化神器–line_profiler。ine_profiler是Python的一个第三方库,其功能时基于函数的逐行代码分析工具。通过该库,可以对目标函数(允许分析多个函数)进行时间消耗分析,便于代码调优。

由于是第三方工具,使用line_profiler前需要安装

$ pip install line_profiler

安装成功后,我们就可以用@profile注解和kernprof命令来收集代码运行情况。我们将上面求质数和的例子改造成@profile注解的形式。

import math
import profile


@profile
def is_prime(n: int) -> bool:
    for i in range(2, math.floor(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True


@profile
def main():
    s = 0
    for i in range(2, 1000000):
        if is_prime(i):
            s += i
    print(s)


if __name__ == "__main__": 
    main()

然后用运行kernprof命令

$ kernprof -lv main.py

其中,参数-l表示用line-by-line profiler代替cProfile。要想@profile注解生效,一定要加-l参数。分析结果会保存到.lprof文件里,加上-v参数可以将结果写入文件后展示在控制台。可以用line_profiler命令查看分析结果:

$ python -m line_profiler <.lprof文件>

解决瓶颈

选择合适的算法和数据结构

利用profiling工具我们可以轻松找到程序的瓶颈所在。接下来就是如何解决瓶颈。有统计,90%的程序性能问题与算法和数据结构有关。选择合适的算法对提升程序性能最关重要。比如,如果要对包含上千元素的列表进行排序,不要用时间复杂度为 O ( n 2 ) O(n^2) O(n2)的冒泡排序,用快速排序(时间复杂度为 O ( n log ⁡ n ) O(n\log_n) O(nlogn))速度会快很多。

上面的例子讲的是算法对性能的影响。选择恰当的数据结构对性能影响也很大。比如,对海量数据进行查找。如果我们用列表存储数据,那么查找指定元素的时间复杂度为 O ( n ) O(n) O(n);但如果我们用二叉树来存储,那么查找速度将提升为 O ( log ⁡ n ) O(\log_n) O(logn);如果我们用哈希表来存储,那么查找速度将变为 O ( 1 ) O(1) O(1)

在描述算法复杂度时,我们通常使用大O标注法。大O标注法定义了算法所需时间的上界。例如插入排序,在最佳情况下需要线性时间,在最坏情况下需要二次时间。于是我们插入排序的时间复杂度是 O ( n 2 ) O(n^2) O(n2),这是算法所需时间的上界,任何情况下都不会超过这个时间。

我整理了Python中常见操作的时间复杂度,供大家参考。
∗ ∗ ∗ \ast \ast \ast

除了选择合适的算法和数据结构外,Python开发过程中也有一些技巧可以提升程序执行速度。

多用列表推导式

能用列表推导式的地方尽量用列表推导式。比如找出10000以内3的倍数,我们可以这么写:

l = []
for i in range (1, 10000):
    if i%3 == 0:
        l.append(i)

用列表推导式来写会更好,不但代码简洁,性能也比上面的代码高,因为列表推导式比append性能高。

l = [i for i in range (1, 100000) if i%3 == 0]

两段代码运行时间(%%timeit)对比

在这里插入图片描述

可见,循环100次取平均时间,列表推导式比用append要快。

少用.操作

开发中尽量避免使用.操作,比如

import math
val = math.sqrt(60)

应该替换为

from math import sqrt
val = sqrt(60)

以为当我们用.调用方法时, 会先调用__getattribute()____getattr()__ ,这两个方法中都包含一些字典操作,这些操作是会耗时的。

在这里插入图片描述

从上面的测试看,不用.要快很多。所以多用 from module import function 直接将方法引入,避免用.调用方法。

善用多重赋值

如果遇到连续变量赋值,比如

a = 2
b = 3
c = 4
d = 5

建议写成

a, b, c, d = 2, 3, 4, 5

避免使用全局变量

Python有global关键字声明或关联全局变量。但是处理全局变量比局部变量要花费更多时间。因此如无必要,勿用全局变量。

尽量使用库方法

一个功能如果Python标准库或第三方库已经提供,就用库方法,不要自己去实现。库方法都是经过高度优化的,甚至很多底层是C语言实现的,我们自己写的方法大概率不会比库方法更高效,并且自己写也不符合DRY精神。

用join拼接字符串

很多语言都是用+拼接字符串,当然Python也支持用+拼接字符串,但我更推荐用join()方法来拼接字符串,因为join()拼接字符串比+要快。+会创建新字符串并将旧字符串的值复制过去,而join()不会。

善用生成器

当我们要处理包含大量数据的列表时,用生成器语法会更快一点。

利用加速工具

有很多项目致力于通过提供更好的运行环境或运行时优化来提升Python的速度。其中成熟的有PyPy和Numba。PyPy比CPython快4.5倍;而Numba是一个JIT编译器,能将Python代码编译成机器码,极大提升科学计算的速度。所以如果条件允许,可以使用上面2个工具来加速Python代码。

用C/C++/Rust实现核心功能

C/C++/Rust都比Python快很多。Python的强大之处是可以和其他语言绑定。所以当处理某些对性能敏感的功能时,我们可以考虑用C/C++/Rust实现核心功能,然后绑定到Python语言上。Python中很多库都是这么做的,比如Numpy, Scipy, Pandas, PyPolars等。

使用最新版本的Python

Python的核心团队也在不懈地优化Python的性能。每一次新版本的发布都比上一版本更加优化,速度也更快。就在前不久,Python发布了最新的3.11.0,这个版本的性能得到极大提升,比3.10性能提升10% - 60%,比Python 2.7 还快 5%。所以,在条件允许的情况下,尽可能用更新版本的Python或获得性能上的提升。

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

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

相关文章

99页4万字XX大数据湖项目建设方案

目 录 1. 项目综述 1.1. 项目背景 1.2. 项目目标 1.3. 项目建设路线 2 需求分析 2.1功能需求 2.1.1 统一数据接入 2.1.2 数据迁移 2.1.3 数据范围与ETL 2.1.4 报表平台 2.1.5 安全管理 2.1.6 数据治理 2.2非功能需求 2.2.1运维保障需求 2.2.2可用性需求 2.2.3可…

MQTT 具备那些特征?

目录 1、MQTT 中的 QoS&#xff08;消息服务质量&#xff09; &#xff08;1&#xff09;为什么服务质量&#xff08;QoS&#xff09;很重要? &#xff08;2&#xff09;QoS 在 MQTT 中是如何工作的? &#xff08;3&#xff09;如何选择正确的 QoS 级别 &#xff08;4&a…

Java开发中Word转PDF文件5种方案横向评测

Java开发中Word转PDF文件5种方案横向评测 前段时间接了个项目&#xff0c;需要各种处理Word模板、转PDF、签章等等&#xff0c;非常头疼&#xff0c;其中光是一个word转PDF就折磨我好久&#xff0c;实现转换很简单&#xff0c;但是效果总是达不到满意&#xff0c;于是我把市面…

【Linux】关于普通用户无法使用sudo指令的解决方案

文章目录前言解决方案结语前言 在这篇博客中&#xff0c;测试 rm -rf 删除文件时无视权限暴力删除的效果时&#xff0c;使用了 sudo 指令。 但是sudo指令是不能直接使用的&#xff0c;需要修改一些设置。 当时我遇到这个问题时&#xff0c;困惑了许久&#xff0c;查找解决方…

JVM执行引擎

文章目录学习资料执行引擎概述工作过程Java代码编译和执行的过程什么是解释器&#xff08;Interpreter&#xff09;&#xff0c;什么是JIT编译器&#xff1f;为什么说Java是半编译半解释型语言&#xff1f;机器码、指令、汇编语言、高级语言机器码指令指令集汇编语言高级语言字…

UE5实现PS图层样式投影效果

一、PS图层样式投影效果 1、创建材质函数 MF_PS_Style_Shadow 公开到库&#xff08;可选&#xff09; 定义 function input。 Shadow代码&#xff1a; /** PS图层样式投影效果param {UVs} texture coordinateparam {TextureObject} texture objectparam {TextureSize} …

十、children的深入用法-React.Children对象上的方法

目标 理解什么是children掌握React.Children对象上的方法 知识点 什么是children上图中我们看到了&#xff0c;我们之前学过的React.createElement方法&#xff0c;现在大家发现jsx的内容&#xff0c;全部都体现在了该方法上&#xff1b;那么React.createElement其实是有三个…

专精特新企业数据集两份数据

专精特新企业数据集 一、三批专精特新上市、非上市公司数据分布 1、时间截止至2021年8月 2、区域范围&#xff1a;上市和非上市公司两大板块&#xff0c;涵盖申万一级行业 3、指标说明&#xff1a; 包含如下内容&#xff1a;专精特新上市公司名单汇总、第一批专精特新上市公…

opencv 入门学习

opencv 演示 输入说明 原图在顶层后然后再去按键&#xff0c;不然会失效&#xff08;未知原因&#xff09; 1.roberts 边缘检测 2.sobel算子 3.Canny算子 4.Laplace算子 5.Canny算子&#xff0c;轮廓显示 空格 人脸检测准备一张图片效果 默认显示原图和灰阶图 roberts 边缘…

MySQL版本号6和7去哪了

问题 MySQL版本号6和7去哪了 详细问题 笔者起初误以为MySQL版本号6和7可能由于存在诟病不受欢迎或由于MySQL版本迭代过快导致未能在市场上流行 但是在浏览MySQL官网注意到 MySQL在2017年发布了新的版本8.0,但是在此之前的上一一个版本是5.7,40&#xff0c;那么中间的6和7去哪…

并发编程永远绕不开的难题,跟着大牛带你Java并发编程从入门到精通

我们知道&#xff0c;很多框架或者自研组件的底层&#xff0c;都或多或少涉及到并发编程方面的技术点。 比如&#xff1a;在一些本地缓存组件中&#xff0c;当本地缓存过期后&#xff0c;需要从数据库加载数据&#xff0c;这个阶段中就会涉及到线程并发请求的处理&#xff1b;在…

微信小程序云开发

概念 小程序云开发&#xff0c;让前端程序员拥有后端的能力云函数 &#xff08;nodejs&#xff09;云数据库 &#xff08;mogodb&#xff09;云存储前端写好云函数 > 上传到云服务器 >实现自定云部署前端去调用云函数>间接通过云函数对数据库的操作前端>全栈 注意…

DSP之寄存器映射和CDM文件

DSP之寄存器映射和CDM文件 RAM&#xff1a;程序运行速度快&#xff0c;关掉电源&#xff0c;程序会丢失。 Flash&#xff1a;程序运行速度慢&#xff0c;关掉电源&#xff0c;程序不会丢失。 所以&#xff0c;程序一般存到Flash中&#xff0c;在运行的时候&#xff0c;由CPU将…

2010-2019年208个地级市城乡收入差距泰尔指数

2010-2019年208个地级市城乡收入差距泰尔指数 1、数据来源&#xff1a;各省的统计NJ以及部分地级市的NJ&#xff08;主要是地级市的城镇化率&#xff09; 城镇化率为常驻人口城镇化率而非户籍人口城镇化率。附件中也包含各个地级市的城镇化率&#xff0c;农村人均可支配收入2…

Linux开发工具(1)——yum

文章目录软件包管理器 —— yum安装软件的三个问题Linux开源生态yum查找软件yum下载软件yum删除软件配置yum源Linux下的工具本质也是指令 , 下面我会介绍几个常用的工具 , 分别是yum(相当于是手机上的应用商店 , 可以在里面下载工具 ) vim&#xff08;多模式编辑器&#xff09;…

【毕业设计】深度学习行人车辆流量计数系统 - 目标检测 python

文章目录0 前言1. 目标检测概况1.1 什么是目标检测&#xff1f;1.2 发展阶段2. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程3 最后0 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff…

机器学习-SVM算法

文章目录支持向量机1. 间隔与支持向量1.1. 点到超平面的距离1.2. 去掉绝对值1.3. 最大间隔2. 对偶问题2.1. 引入拉格朗日乘子2.2. 求偏导2.3. 得到对偶问题2.4. 求解内层函数 minw,bL(w,b,α)min_{w,b} L(w,b,\alpha)minw,b​L(w,b,α)2.5. 求解外层函数 maxαminw,bL(w,b,α)m…

.ko 加载报错 “unknown symbol in module or invalid parameter” 排查解决方法

.ko 加载报错 “unknown symbol in module or invalid parameter” 排查解决方法 问题来源 今天参照Sigmastar的文档&#xff0c;修改config重新编译kernel&#xff0c;打开板上RNDIS虚拟网口。 按照步骤重编后&#xff0c;在demo.sh加入insmod指令&#xff0c;按顺序在启动…

【计算机网络】—网络编程(socket)02

目录 一、网络编程的概念 二、UDP数据报套接字编程 2.1 回显服务器代码 2.2 翻译程序&#xff08;英译汉&#xff09; 三、TCP数据报套接字编程 3.1回显服务器 3.2 翻译服务器 一、网络编程的概念 网络编程&#xff1a;指网络上的主机&#xff0c;通过不同的进程&#x…

openlayer+ol-ext 裁剪 天地图 中国或者其他省份 范围进行展示

地图未裁剪或遮盖效果&#xff08;天地图&#xff09; 效果1.crop: 1.1裁剪天地图里面效果 参数&#xff1a; inner: true 1.2裁剪天地图外面 参数&#xff1a; inner: false 核心代码&#xff1a; let crop new Crop({feature: feature[0],inner: false,});vecLayer.addF…