引言
如果你想优化python程序的运行效率,你会从哪里下手?
首先,我们要找到「性能瓶颈」,比如哪些函数的运行效率低、计算时间长,然后分析原因,针对性地进行优化。
最朴素的方法是,在你预估的函数前后加上time.perf_counter()1,然后得出这个函数的运行时间。但这种方法不适用于具有大量函数调用的程序。
import time
start = time.perf_counter()
func()
print("cost %s second" % (time.perf_counter() - start))
python有很多性能分析工具,功能强大,可以帮助我们对各种函数的性能进行分析。
这篇文章就介绍三种简单的性能分析工具。其中cProfile和timeit都是python标准库中的工具,无需安装第三方库,看完本篇文章,教你快速上手。
1. cProfile:最便捷的性能分析
推荐指数: ⭐️⭐️⭐️⭐️
cProfile是python标准库中一个使用便捷、开销合理的 C 扩展,适用于分析长时间运行的程序。2
不想阅读太多细节的小伙伴,可以直接看代码,非常简单,直接在cProfile.run中输入目标函数名称:
import cProfile
import test_module
cProfile.run('test_module.func()')
在程序运行完后,就会在控制台打印test_module.func()运行的具体耗时情况:
538569 function calls (519249 primitive calls) in 71.446 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
44 0.002 0.000 0.197 0.004 ImageFile.py:154(load)
18 0.000 0.000 0.001 0.000 ImageFile.py:278(load_prepare)
1 0.000 0.000 0.000 0.000 ImageFile.py:30(<module>)
1 0.000 0.000 0.000 0.000 ImageFile.py:313(StubImageFile)
1 0.000 0.000 0.000 0.000 ImageFile.py:339(Parser)
13 0.001 0.000 1.348 0.104 ImageFile.py:478(_save)
每一行依次列出了各个子函数的运行分析信息:
-
ncalls
调用次数 -
tottime
在给定函数中花费的总时间(不包括调用子函数的时间) -
percall
tottime除以ncalls的商 -
cumtime
是在这个函数和所有子函数中花费的累积时间(从调用到退出)。 -
percall
是cumtime除以原始调用次数的商 -
filename:lineno(function)
提供每个函数的各自信息
保存性能数据
如果你想保存这些性能数据,在run函数的参数中加上’restats’文件名:
cProfile.run('main()','restats')
就会保存到当前目录下的restats文件中。
查看性能数据
加载这些数据,可以进行后续的比较分析。
import pstats
from pstats import SortKey
# 加载保存到restats文件中的性能数据
p = pstats.Stats('restats')
# 打印所有统计信息
p.strip_dirs().sort_stats(-1).print_stats()
查看耗时最多的子函数
最常用的是,查看耗时最多的函数排序,比如前十个:
# 打印累计耗时最多的10个函数
p.sort_stats(SortKey.CUMULATIVE).print_stats(10)
# 打印内部耗时最多的10个函数(不包含子函数)
p.sort_stats(SortKey.TIME).print_stats(10)
上面两个例子分别用到用SortKey.CUMULATIVE
或SortKey.TIME
参数。
SortKey的具体参数如下表所示3:
查看特定名称函数的耗时
例如,我们在耗时排序中,发现一些加载函数的耗时较大,可以单独统计下包含load的调用信息:
# 打印包含load的函数名的调用者统计信息
p.print_callers(.5, 'load')
0.5表示列表被剔除到其原始大小的50%,然后保留包含load的行:
例如,还可以查看哪些类的初始化__init__函数耗时较多:
# 按耗时排序,依次打印类的__init__方法的统计信息
p.sort_stats(SortKey.CUMULATIVE).print_stats('__init__')
2. timeit:计算小代码片段的耗时
推荐指数: ⭐️⭐️
timeit适用于测量小段代码的耗时,可以在python代码中调用,也可以在命令行中调用4。timeit的设计避免了许多用于测量执行时间的常见陷阱。
测试你写的函数的运行时间:
def test():
"""Stupid test function"""
L = [i for i in range(100)]
if __name__ == '__main__':
import timeit
print(timeit.timeit("test()", setup="from __main__ import test"))
测试一个语句的运行时间:
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
50000 loops, best of 5: 4.26 usec per loop
使用方式:
python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]
-
-n N, --number=N
执行“语句”多少次 -
-r N, --repeat=N
重复计时器多少次(默认 5) -
-s S, --setup=S
最初执行一次的语句(默认pass) -
-p, --process
测量进程时间,而不是挂钟时间,使用time.process_time() 代替time.perf_counter(),这是默认值
3.3 版中的新功能。 -
-u, --unit=U
指定定时器输出的时间单位;可以选择 nsec、usec、msec 或 sec
3.5 版中的新功能。 -
-v, --verbose
打印原始计时结果;重复以获得更多数字精度 -
-h, --help
打印一个简短的使用信息并退出
3. IDE中的性能分析
推荐指数: ⭐️⭐️⭐️
如果你有IDE,那么很多IDE都提供了性能分析的相关工具。
以pycharm 专业版为例,从Run中点击Profile,即可对当前python脚本进行性能分析。
程序运行结束后,pycharm会生成以下统计:
这里列出了子函数的名称、调用次数、耗时等信息,双击函数名可以查看对应源码。
小结
本文整理了三个常用的python性能分析工具,帮助你定位python程序的性能瓶颈。
如果对你有所帮助,欢迎一键三连,支持下博主。