前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python性能优化全攻略:10个实用技巧大公开

Python性能优化全攻略:10个实用技巧大公开

原创
作者头像
小羽网安
发布2024-06-21 22:48:05
1660
发布2024-06-21 22:48:05
举报
文章被收录于专栏:编程

Python性能优化全攻略:10个实用技巧大公开

前言

Python,作为一种动态类型的解释性语言,确实在执行速度上可能不如C这样的静态类型的编译语言。但是,通过一些技巧和策略,我们可以显著提升Python代码的性能。

本文将探讨如何通过优化方法使Python代码运行得更快、更高效。我们将利用Python的timeit模块来精确测量代码的执行时间。

注意:timeit模块在默认情况下会重复执行代码一百万次,以确保测量结果的准确性和稳定性

代码语言:javascript
复制
def print_hi(name):
    print(f'Hi, {name}')
    
if __name__ == '__main__':
    # 执行print_hi('PyCharm')方法
    t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("PyCharm")')
    t.timeit()

如何计算python脚本的运行时间呢?在time模块中time.perf_counter()提供了一个高精度的计时器,适合测量短时间,例如

代码语言:javascript
复制
import time
​
# 记录程序开始时间
start_time = time.perf_counter()
​
# 你的代码逻辑
# ...
​
# 记录程序结束时间
end_time = time.perf_counter()
​
# 计算程序运行时间
run_time = end_time - start_time
​
print(f"程序运行时间:{run_time} 秒")

介绍

一、I/O密集型操作

I/O密集型操作(Input/Output Intensive Operation)指的是那些在执行过程中,大部分时间都花在等待输入/输出操作完成的程序或任务。I/O操作包括从磁盘读取数据、写入数据到磁盘、网络通信等。这些操作通常涉及到硬件设备,因此它们的执行速度受到硬件性能和I/O带宽的限制。

他们的特点有:

  1. \1. 等待时间:程序在执行I/O操作时,往往需要等待数据从外部设备传输到内存,或从内存传输到外部设备,这会导致程序的执行被阻塞。
  2. \2. CPU利用效率:由于I/O操作的等待时间,CPU在这段时间内可能处于空闲状态,导致CPU利用率不高。
  3. \3. 性能瓶颈:I/O操作的速度往往成为程序性能的瓶颈,尤其是在数据量大或传输速度慢的情况下。

例如,使用I/O密集型操作print,运行一百万次

代码语言:javascript
复制
import time
import timeit
def print_hi(name):
    print(f'Hi, {name}')
    return
if __name__ == '__main__':
    start_time = time.perf_counter()
    # 执行print_hi('PyCharm')方法
    t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("PyCharm")')
    t.timeit()
    end_time = time.perf_counter()
    run_time = end_time - start_time
    print(f"程序运行时间:{run_time} 秒")

运行结果为3s

image-20240621223845875
image-20240621223845875

而不使用i/o操作执行一个方法,即调用这个print_hi('xxxx')空方法,不使用print(),程序明显快了不少

代码语言:javascript
复制
def print_hi(name):
    # print(f'Hi, {name}')
    return
image-20240621115008544
image-20240621115008544

如果代码中必要的时候,例如文件读写,可以使用如下方法提高效率

  1. \1. 异步I/O:使用异步编程模式例如asyncio,允许程序在等待I/O操作完成时继续执行其他任务,从而提高CPU利用率。
  2. \2. 缓冲:使用缓冲区暂存数据,减少I/O操作的频率。
  3. \3. 并行处理:并行执行多个I/O操作,以提高整体的数据处理速度。
  4. \4. 优化数据结构:选择合适的数据结构,减少数据的读取和写入次数。

二、使用生成器生成列表、字典

在Python 2.7及其后续版本中,引入了对列表、字典和集合生成器的改进,这些改进让数据结构的构建过程更加简明和高效。

1、传统方法
代码语言:javascript
复制
def fun1():
    list=[]
    for i in range(100):
        list.append(i)
        
if __name__ == '__main__':
    start_time = time.perf_counter()
    t = timeit.Timer(setup='from __main__ import fun1', stmt='fun1()')
    t.timeit()
    end_time = time.perf_counter()
    run_time = end_time - start_time
    print(f"程序运行时间:{run_time} 秒") # 输出结果:程序运行时间:3.3872999000595883 秒
2、使用生成器优化代码

注:为了方便以下内容皆省略主函数main的代码部分

代码语言:javascript
复制
def fun1():
    list=[ i for i in range(100)] # 程序运行时间:2.1053185999626294 秒

从上述的推导式程序中可以看出,除了理解更简洁、更容易阅读之外,它也更快。这使得此方法成为生成列表和循环的首选方法。

三、避免字符串连接,使用join()

join() 是一个字符串方法,在Python中用于将序列中的元素连接(或拼接)成一个字符串,通常使用特定的分隔符。他的优点通常为:

  1. \1. 效率高join() 是连接字符串的高效方法,尤其是当处理大量字符串时,它通常比使用 + 操作符或 % 格式化更快,在连接大量字符串时,join() 方法通常比逐个连接更节省内存。
  2. \2. 简洁性join() 使得代码更加简洁,避免了重复的字符串连接操作。
  3. \3. 灵活性:可以指定任何字符串作为分隔符,这为字符串拼接提供了极大的灵活性。
  4. \4. 广泛的用途:不仅可以用于字符串,还可以用于列表、元组等序列类型,只要元素可以被转换成字符串。

举例:

代码语言:javascript
复制
def fun1():
    obj=['hellow','my','name','is','xiaoyu','!']
    s=""
    for i in obj:
        s+=i       # 程序运行时间:0.3610708999913186 秒

使用 join() 来实现字符串拼接:

代码语言:javascript
复制
def fun1():
    obj=['hellow','my','name','is','xiaoyu','!']
    "".join(obj) # 程序运行时间:0.18804279994219542 秒

使用join()将函数的执行时间从0.36秒减少到0.18秒

四、使用Map代替循环

在多数场景中,传统的for循环可以被更为高效的map()函数所替代。map()*是一个Python内置的*高阶函数,它能够将指定的函数应用于各种可迭代的数据结构,如列表、元组或字符串。使用map()的主要优势在于,它提供了一种更为简洁且高效的数据处理方式,避免了编写显式的循环代码。

传统的循环方式:

代码语言:javascript
复制
def fun1():
    arr=["hello", "my", "name", "is", "xiaoyu", "!"]
    new = []
    for i in arr:
        new.append(i) # 程序运行时间:0.31288250000216067 秒

使用map()函数做相同的功能:

代码语言:javascript
复制
def fun2(x):
    return x

def fun1():
    arr=["hello", "my", "name", "is", "xiaoyu", "!"]
    map(fun2,arr) # 程序运行时间:0.18387670000083745 秒

对比之后,使用map()节省了将近一半的时间,大大提升了运行效率

五、选择正确的数据结构

选用恰当的数据结构对提升Python代码的执行效率至关重要。各类数据结构都针对特定操作进行了优化,合理选择能够加速数据的检索、添加和移除过程,进而增强程序的整体运行效能。

例如,判断容器内的元素的时候,字典的查找效率高于列表,但是是在大量数据的情况下,少量数据恰恰相反

代码语言:javascript
复制
# 使用少量数据进行测试
def fun1():
    arr=["hello", "my", "name", "is", "xiaoyu", "!"]
    'hello' in arr
    'my' in arr      # 程序运行时间:0.11527379998005927 秒

def fun1():
    arr={"hello", "my", "name", "is", "xiaoyu", "!"}
    'hello' in arr
    'my' in arr    # 程序运行时间:0.17057139997836202 秒
    
    
# 使用 numpy 进行随机生成100个整数
def fun1():
    nums = {i for i in np.random.randint(100, size=100)}
    1 in nums    # 程序运行时间:14.48330469999928 秒
    
def fun1():
    nums = {i for i in np.random.randint(100, size=100)}
    1 in nums    # 程序运行时间:13.411826699972153 秒

看到了在少量数据的情况下list执行效率是要大于dict的,但是在大量数据的情况下,dict的效率大于list

如果有频繁的新增、删除操作,新增、删除的元素数量又很多时,list的效率不高。此时,应该考虑使用collections.dequecollections.deque是双端队列,同时具备栈和队列的特性,能够在两端进行 O(1)复杂度的插入和删除操作。

collections.deque的使用

代码语言:javascript
复制
from collections import deque  
def fun1():
    arr=deque()# 创建一个空的deque
    for i in range(1000000):
        arr.append(i)
# 程序运行时间:0.05507110000002058 秒

def fun1():
    arr=[]
    for i in range(1000000):
        arr.append(i)
# 程序运行时间:0.06128990000001977 秒

list的查找操作也非常耗时。当需要在list频繁查找某些元素,或频繁有序访问这些元素时,可以使用bisect维护list对象有序并在其中进行二分查找,提升查找的效率。

六、避免不必要的函数调用

在Python编程中,优化函数调用次数对于提升代码效率至关重要。过多的函数调用不仅增加了开销,还可能消耗额外的内存,从而拖慢程序的运行速度。为了提升性能,我们应尽量减少不必要的函数调用,并尝试将多个操作合并成一个,以此来减少执行时间和资源消耗。这样的优化策略有助于我们编写更高效、更快速的代码。

七、避免不必要的import

虽然Python的import语句相对较快,但每个import都会涉及到查找模块、执行模块代码(如果还没有被执行过)、并将模块对象放入到当前命名空间中。这些操作都需要一定的时间和内存。当你不必要地导入模块时,就会增加这些开销。

八、避免使用全局变量

代码语言:javascript
复制
import math

size=10000
def fun1():
    for i in range(size):
        for j in range(size):
            z = math.sqrt(i) + math.sqrt(j) 
# 程序运行时间:15.630933800013736 

许多程序员刚开始会用 Python 语言写一些简单的脚本,当编写脚本时,通常习惯了直接将其写为全局变量,例如上面的代码。但是,由于全局变量和局部变量实现方式不同,定义在全局范围内的代码运行速度会比定义在函数中的慢不少。通过将脚本语句放入到函数中,通常可带来 15% - 30% 的速度提升。

代码语言:javascript
复制
import math

def fun1():
    size = 10000
    for i in range(size):
        for j in range(size):
            z = math.sqrt(i) + math.sqrt(j)  
 # 程序运行时间:14.933845699997619 秒

九、避免模块和函数属性访问

代码语言:javascript
复制
import math # 不推荐写法

def fun2(size: int):
    result = []
    for i in range(size):
        result.append(math.sqrt(i))
    return result

def fun1():
    size = 10000
    for _ in range(size):
        result = fun2(size) 
# 程序运行时间:10.154493000009097 秒

每次使用.(属性访问操作符时)会触发特定的方法,如__getattribute__()__getattr__(),这些方法会进行字典操作,因此会带来额外的时间开销。通过from import语句,可以消除属性访问。

代码语言:javascript
复制
from math import sqrt # 推荐写法:用到哪个模块就导哪个模块

def fun2(size: int):
    result = []
    for i in range(size):
        result.append(sqrt(i))
    return result

def fun1():
    size = 10000
    for _ in range(size):
        result = fun2(size)
# 程序运行时间:8.960758000030182 秒

十、减少内层for循环的计算

代码语言:javascript
复制
import math

def fun1():
    size = 10000
    sqrt = math.sqrt
    for x in range(size):
        for y in range(size):
            z = sqrt(x) + sqrt(y) # sqrt() 求非负实数的平方根
# 程序运行时间:14.267008299939334 秒

在上面代码中sqrt(x)位于内测for循环,每次循环都会重新计算,增加不必要的时间开销

代码语言:javascript
复制
import math

def fun1():
    size = 10000
    sqrt = math.sqrt
    for x in range(size):
        sqrt_x=sqrt(x) # 在外层for循环进行计算
        for y in range(size):
            z = sqrt_x + sqrt(y) 
# 程序运行时间:8.499037600005977 秒

总结

通过这些方法,我们可以有效地提高Python代码的性能,使其在处理复杂任务时更加快速和高效。记住,性能优化是一个持续的过程,需要根据具体情况不断调整和改进,python运行速度的优化方法不限于以上方法,还有很多,如有大佬路过,请多指教。

原文:Python性能优化全攻略:10个实用技巧大公开

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Python性能优化全攻略:10个实用技巧大公开
    • 前言
      • 介绍
        • 一、I/O密集型操作
        • 二、使用生成器生成列表、字典
        • 三、避免字符串连接,使用join()
        • 四、使用Map代替循环
        • 五、选择正确的数据结构
        • 六、避免不必要的函数调用
        • 七、避免不必要的import
        • 八、避免使用全局变量
        • 九、避免模块和函数属性访问
        • 十、减少内层for循环的计算
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档