cProfile 提供了 Python 程序的 稳定性性能分析;即通过监控所有函数调用、函数返回和异常事件,并对这些事件之间的间隔进行精确计时。实现统计程序的各个部分执行的频率和时间。cPorfile 的性能分析往往可以使用增加很小的处理开销,提供有关 Python 程序执行的大量运行时的统计信息。
cProfile 的文档:https://docs.python.org/zh-cn/3/library/profile.html
简单用法样例
当我们需要分析一个 Python 脚本的运行性能时,我们可以简单地将脚本入口函数封装起来,然后使用 cProfile.run()
方法执行这个函数。例如:
import cProfile
def main():
"""封装的脚本入口函数"""
if __name__ == "__main__":
cProfile.run("main()")
cProfile 的
run
方法文档:https://docs.python.org/zh-cn/3/library/profile.html?#profile.run
如需添加全局命名空间和局部命名空间调用 run
方法,则可以使用 cProfile
的 runctx
方法。
cProfile 的
runctx
方法文档:https://docs.python.org/zh-cn/3/library/profile.html?#profile.runctx
上述脚本在脚本运行完成后,会在控制台打印类似信息:
4140186 function calls (4071373 primitive calls) in 10.881 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
661 0.001 0.000 0.011 0.000 <__array_function__ internals>:177(all)
8564 0.007 0.000 0.062 0.000 <__array_function__ internals>:177(amax)
8562 0.006 0.000 0.065 0.000 <__array_function__ internals>:177(amin)
656 0.001 0.000 0.011 0.000 <__array_function__ internals>:177(any)
......
其中:
列序号 | 列名 | 含义 |
---|---|---|
1 | ncalls |
调用次数 |
2 | tottime |
在指定函数中消耗的总时间(不包括调用子函数的时间) |
3 | percall |
是以 tottime 除以 ncalls 的商 |
4 | cumtime |
指定的函数及其所有子函数(从调用到退出)消耗的累积时间。这个数字对于递归函数来说是准确的。 |
5 | percall |
是 cumtime 除以原始调用(次数)的商(即:函数运行一次的平均时间) |
6 | filename:lineno(function) |
提供相应数据的每个函数 |
如果需要将上述结果排序,则可以使用 run
方法的 sort
参数,该参数需传入字符串或 SortKey
枚举类作为实参。排序参数 具体包括:
有效字符串参数 | 有效枚举参数 | 含义 |
---|---|---|
"calls" |
SortKey.CALLS |
调用次数 |
"cumulative" |
SortKey.CUMULATIVE |
累积时间 |
"cumtime" |
- | 累积时间 |
"file" |
- | 文件名 |
"filename" |
SortKey.FILENAME |
文件名 |
"module" |
- | 文件名 |
"ncalls" |
- | 调用次数 |
"pcalls" |
SortKey.PCALLS |
原始调用计数 |
"line" |
SortKey.LINE |
行号 |
"name" |
SortKey.NAME |
函数名称 |
"nfl" |
SortKey.NFL |
名称/文件/行 |
"stdname" |
SortKey.STDNAME |
标准名称 |
"time" |
SortKey.TIME |
内部时间 |
"tottime" |
- | 内部时间 |
排序参数的文档:https://docs.python.org/zh-cn/3/library/profile.html?#pstats.Stats.sort_stats
使用 Profile 类进行性能分析
cProfile.Profile 类的文档:https://docs.python.org/zh-cn/3/library/profile.html?#profile.Profile
扫描二维码关注公众号,回复: 15053491 查看本文章
如需要更精确的性能分析,我们可以通过 Profile 类实现。该类支持直接格式化性能分析结果,而不需要将性能分析数据写入文件。该类有如下常用方法:
方法名 | 用途 |
---|---|
enable() |
开始收集分析数据。(用于精确控制分析范围) |
disable() |
停止收集分析数据。(用于精确控制分析范围) |
create_stats() |
停止收集分析数据,并在内部将结果记录为当前 profile。 |
print_stats(sort=-1) |
根据当前性能分析数据创建一个 Stats 对象并将结果打印到 stdout。 |
dump_stats(filename) |
将当前 profile 结果写入 filename。 |
run(cmd) |
通过 exec() 对该命令进行性能分析。 |
runctx(cmd, globals, locals) |
通过 exec() 并附带指定的全局和环境变量对该命令进行性能分析。 |
runcall(func, /, *args, **kwargs) |
对 func(*args, **kwargs) 进行性能分析。 |
需要注意的是,必须进行了 enable()
、run()
、runctx()
、runcall()
中任意一个方法后,profile
实例才能用于构造 Stats
类统计对象,否则会报错:
TypeError: Cannot create or construct a <class 'pstats.Stats'> object from <cProfile.Profile object at 0x000001BCE9C332E0>
使用 cProfile.Profile
+ pstat.Stats
进行性能分析的样例如下:
import cProfile
import pstats
def main():
"""封装的脚本入口函数"""
preprocessing() # 待分析函数执行前的初始化(不需要分析性能的部分)
profiler.enable()
processing() # 待分析函数(需要被性能分析的部分)
profiler.disable()
reprocessing() # 待分析函数执行后的后处理(不需要分析性能的部分)
if __name__ == "__main__":
profiler = cProfile.Profile() # 实例化 Profile 类
main() # 调用封装的脚本入口函数
stats = pstats.Stats(profiler).sort_stats(pstats.SortKey.CUMULATIVE) # 根据 profile 实例构造 Stats 类,并执行排序
stats.print_stats() # 将排序后的汇总统计结果打印到 stdout
在上例中,我们也可以使用 run()
、runctx()
、runcall()
等其他方法调用被分析函数。
使用 pstat 进行分析
pstats.Stats
类的文档:https://docs.python.org/zh-cn/3/library/profile.html?#pstats.Stats
Stats
类基于 filename
文件或 Profile
实例构造。输出会被打印到 stream 参数所指定的流中。该类有如下常用方法:
方法名 | 含义 |
---|---|
strip_dirs() |
去除文件的所有前导路径信息 |
add(*fileanmes) |
将额外的性能分析信息累加到当前性能分析对象中 |
dump_stats(filename) |
将当前性能分析对象中的信息写出到文件 |
sort_stats(*keys) |
将当前性能分析对象中的记录排序(参数详见上文 排序参数 表) |
reverse_order() |
将当前性能分析对象中的记录倒序 |
print_callers(*restrictions) |
打印哪些函数调用了 restrictions 函数 |
print_callees(*restrictions) |
打印 restrictions 函数中调用了哪些函数 |
get_stats_profile() |
返回一个 StatsProfile 实例 |
其中,需要注意的是:通过 get_stats_profile()
,我们可以得到 StatsProfile
实例,该实例的 func_profiles
即字典格式的性能分析信息。如果需要对分析结果做进一步处理,则可以使用这个字典;但是该字典中没有保留每次调用的 callers
信息。