使用python进入一个熟练的状态之后就会思考提升代码的性能,尤其是python的执行效率还有很大提升空间(委婉的说法)。面对提升效率这个话题,python自身提供了很多高性能模块,很多大牛开发出了高效第三方包,可谓是百花齐放。下面根据我个人使用总结出提升性能的几个层面和相关方法。
python代码优化:
- 语法层面
- 高效模块
- 解释器层面
语法层面
- 变量定义
- 数据类型
- 条件判断
- 循环
- 生成器
变量定义
- 多使用局部变量少使用全局变量,命名空间中局部变量优先搜索
条件判断
- 可以使用字典的key value特性,直接用key命中条件,避免if判断
- 用in操作替换if else判断
- 使用any 或 all 将多个判断一起处理,减少if else的分支
- if条件的短路特性。if a or b这种判断中,如果a是True就不会判断b,所以将True条件写在前面可以节省判断时间。同理 and 判断将假写在前面,后面一个条件不判断
数据类型
- 使用dict 或set查找,替换list或tuple
- 集合的交并补差操作效率非常高。for循环和集合都可以处理的选择集合解决,集合的效率远高于循环
循环
- 用for循环代替while循环,for循环比while循环快
- 使用隐式for循环代替显式for循环。如sum,map,filter,reduce等都是隐式for循环。隐式循环快于显式循环
- 尽量不要打断循环。打断循环的放在外面。有判断条件的语句和与循环不相关的操作语句尽量放在for外面
- 应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数
- 使用生成式替换循环创建
合理使用迭代器和生成器
需要迭代出大量数据的场景,不需要将所有数据创建出来,合理使用生成器减少内存消耗
items_gen = (i for i in range(5000))
>>> items_gen.__sizeof__()
96
items_list = [i for i in ragne(5000)]
>>> items_list.__sizeof__()
43016
高效模块
- collections 数据增强模块
- itertools 高效迭代模块
- array 高效数组
- functool 用于处理函数的高阶函数包
collections
- Counter: 高效的统计库
- defaultdict:带默认值的字典
- ChainMap:高效组合字典的库
- deque: 双端队列,高效插入删除
详细使用参见另一篇专门讲collections的文章 Python原生数据结构增强模块collections
itertools
- chain:多个可迭代对象构建成一个新的可迭代对象
- groupby:按照指定的条件分类,输出条件和符合条件的元素
- from_iteratorable:一个迭代对象中将所有元素类似于chain一样,统一返回
- islice:对迭代器进行切片,能指定start和stop以及步长
详细使用参见另一篇专门讲itertools的文章Python高性能工具迭代标准库itertools
array
array 模块是python中实现的一种高效的数组存储类型。
它和list相似,但是所有的数组成员必须是同一种类型,在创建数组的时候,就确定了数组的类型。
functool
functools.lru_cache
对函数做缓存
lru_cache 是一个装饰器,为函数提供缓存功能。被装饰的函数以相同参数调用时直接返回上一次的结果。
不做缓存
import time
def fibonacci(n):
"""斐波那契函数"""
if n < 2:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
start = time.time()
res = fibonacci(40)
end = time.time()
print(res)
print(end - start)
102334155
32.14816737174988
做缓存
import time
from functools import lru_cache
@lru_cache
def fibonacci(n):
"""斐波那契函数"""
if n < 2:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
start = time.time()
res = fibonacci(40)
end = time.time()
print(res)
print(end - start)
102334155
0.00020623207092285156
使用注意:
- 缓存是按照参数作为键。调用函数时任意一个参数发生变化都不会返回之前缓存结果
- 所有参数必须可哈希hash。也就是说参数只能是不可变对象
解释器层面:
减少python执行过程
python 代码的执行过程为:
- 编译器将源码编译成中间状态的字节码
- 解释器执行字节码,将字节码转成机器码在cpu上运行
python慢的原因主要是因为解释器。解决办法有两个:
一是解决办法是使用C/C++语言重写Python函数,但是这要求程序员对C/C++语言熟悉,且调试速度慢,不适合绝大多数Python程序员。
另外一种非常方便快捷的解决办法就是使用Just-In-Time(JIT)技术。
Just-In-Time(JIT)技术为解释语言提供了一种优化,它能克服上述效率问题,极大提升代码执行速度,同时保留Python语言的易用性。使用JIT技术时,JIT编译器将Python源代码编译成机器直接可以执行的机器语言,并可以直接在CPU等硬件上运行。这样就跳过了原来的虚拟机,执行速度几乎与用C语言编程速度并无二致。
Numba是一个针对Python的开源JIT编译器,由Anaconda公司主导开发,可以对Python原生代码进行CPU和GPU加速。
import time
def fun(x):
total = 0
start = time.time()
for i in range(1,x+1):
total += i
end = time.time()
print(total)
print(end - start)
fun(100000000)
5000000050000000
5.934630393981934
import time
from numba import jit, int32
@jit(int32(int32))
def fun(x):
total = 0
start = time.time()
for i in range(1,x+1):
total += i
end = time.time()
print(total)
print(end - start)
fun(100000000)
5000000050000000
0.1186532974243164
速度有60倍提升