应使用cprofile.profile()实例手动控制启停并dump_stats保存二进制.prof文件,再用pstats加载、strip_dirs、按cumtime排序打印前20行,优先分析cumtime高但tottime低的函数调用链。

cProfile 是 Python 官方最轻量、最可靠的性能分析入口,不用装第三方包,开箱即用;但直接调用 cProfile.run() 或命令行跑完就丢出一屏数字,根本没法定位热点——关键在怎么组织调用、过滤和解读输出。
如何正确启动 cProfile 并保存可复用的分析结果
别用 cProfile.run('your_code()') 交互式跑,它不保留原始统计对象,后续无法筛选或排序。应该用 cProfile.Profile 实例手动控制生命周期:
import cProfile
pr = cProfile.Profile()
pr.enable()
# 这里放你要测的代码,比如:your_function(arg1, arg2)
pr.disable()
pr.dump_stats('profile_output.prof') # 二进制文件,可被 pstats 或 snakeviz 复用
-
pr.enable()和pr.disable()必须成对出现,中间不能有未捕获异常中断,否则统计会不全 - 不要用
pr.create_stats()—— 它已弃用,且只在run()内部自动调用 - 文件后缀名不影响内容,但建议统一用
.prof,避免和.pstat(旧格式)混淆
怎样快速找到真正拖慢执行的函数(不是内置方法)
默认输出里 <built-in method></built-in> 和 <method-wrapper></method-wrapper> 占满屏幕,但它们通常不是瓶颈根源。要用 pstats 过滤并按累计时间排序:
import pstats
stats = pstats.Stats('profile_output.prof')
stats.strip_dirs() # 去掉路径前缀,聚焦函数名
stats.sort_stats('cumtime') # 按 cumulative time 排序(含子调用耗时)
stats.print_stats(20) # 只打前 20 行
- 优先看
cumtime列,不是tottime:前者反映该函数及其所有下层调用总耗时,更能暴露“调用链级联慢”的问题 - 如果某函数
cumtime高但tottime很低,说明它本身快,但反复调用慢子函数(比如循环里调json.loads()) - 用
stats.print_callers('your_function_name')查谁在频繁调它
命令行方式适合快速验证,但要注意参数陷阱
终端里直接跑 python -m cProfile -o output.prof script.py 最省事,但有两个常见误操作:
- 加了
-s cumtime但没加-o:输出直接刷屏,无法保存或重排序;加了-o就别再加-s,排序留到pstats里做 - 想分析某段脚本片段(非整个文件),不能写
python -m cProfile -o out.prof "x=1; y=2; your_func()"—— 会被 shell 当作字符串传给解释器,报SyntaxError;必须封装成独立 .py 文件再 profile - 如果脚本依赖命令行参数,要写成
python -m cProfile -o out.prof script.py --arg1 val1,参数会透传给script.py
为什么有时 profile 结果和实际感知速度不一致
cProfile 统计的是 CPU 时间,对 I/O 密集型(如网络请求、磁盘读写、time.sleep())几乎无感知——这些耗时会“消失”在 tottime 里,只体现在外部等待上。
- 遇到 Web 请求慢、数据库查询卡顿,
cProfile显示主函数tottime几乎为 0,别怀疑工具,这是设计使然 - 此时应配合
line_profiler(逐行)或系统级工具如strace/perf查阻塞点 - 多线程程序中,cProfile 默认只 profile 主线程;启用多线程采样需手动在每个线程里创建独立
Profile实例并合并
真正难的不是跑出 profile 数据,而是区分「CPU 瓶颈」和「等待瓶颈」,以及把 cumtime 高的函数和业务逻辑里的具体路径对应起来——这一步没法靠工具自动完成。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











