Python / 性能分析
用 cProfile 和 Snakeviz 排查 Python 性能问题
2026年7月30日
性能优化的第一步不是修改代码,而是先确认时间花在什么地方。瓶颈可能位于 Python 循环、数据转换、序列化或外部调用;这篇文章使用 cProfile 收集调用统计,再用 Snakeviz 查看热点,最后在相同条件下验证改动是否有效。
先选择采样方式
通常有两种做法:
| 场景 | 建议方式 | 原因 |
|---|---|---|
| 需要分析完整程序流程 | 命令行 | 最接近真实入口,会包含全部初始化与调用步骤。 |
| 只想分析某个函数 | Jupyter Notebook | 可以先完成前置准备,再反覆测试指定函数,迭代更快。 |
1. 用命令行分析完整流程
命令行是最简单的起点。不需要在业务代码中插入 enable() 或计时逻辑,只需用 cProfile 包裹原本的入口命令。假设原本的运行命令是:
python xxx
当需要分析这条命令时,只需在原命令前加上 python -m cProfile:
python -m cProfile xxx
默认输出会列出函数统计表。使用 -s 可以改变排序方式:
python -m cProfile -s tottime xxx
python -m cProfile -s cumulative xxx
-s tottime 按函数自身耗时排序,适合找高频循环或单一计算热点;-s cumulative 按函数及其子函数的总耗时排序,适合找端到端调用链中最重的区块。输出表格中的字段含义如下:
| 字段 | 含义 |
|---|---|
ncalls |
函数调用次数。 |
tottime |
函数自身耗时,不含子函数。 |
第一个 percall |
tottime / ncalls,即函数自身的平均每次耗时。 |
cumtime |
函数及其子函数的累积耗时。 |
第二个 percall |
cumtime / primitive calls,即包含子函数的平均耗时。 |
filename:lineno(function) |
函数所在文件、行号和函数名。 |
终端表格适合快速筛选,但当函数层级很深或多个函数互相调用时,仅看文本输出很难理解完整调用关系。这时可用 -o 保存 profiling 结果,再用 Snakeviz 查看图形化调用链:
python -m cProfile -o profile.pstats xxx
snakeviz profile.pstats
这种方式保留真实的命令行参数、配置和调用链,避免为了 profiling 另外写一段与实际流程不一致的测试脚本,采样前应固定输入、配置等。
2. 在 Notebook 分析指定函数
如果问题已经缩小到某个函数,Notebook 更适合反覆测试。先在前面的 cell 完成前置准备,避免把初始化混入采样;再使用 %prun 保存指定函数的结果:
# 前置准备在采样前完成
input_data = prepare_input()
# 只分析目标函数,并保存结果
%prun -D baseline.pstats target_function(input_data)
完成优化后,使用相同输入重新执行:
%prun -D optimized.pstats target_function(input_data)
之后仍可用 Snakeviz 打开文件:
snakeviz baseline.pstats
snakeviz optimized.pstats
3. 在 Snakeviz 中查看什么
Snakeviz 将函数调用关系展示为可交互的调用图。先找占比最大的节点和最长的调用链,再区分两类时间:
cumulative time:函数加上所有子函数的总耗时。用它定位端到端流程中最重的大区块。tottime:函数自身耗时,不包含子函数。用它发现高频循环或单一计算热点。
点开可疑节点后,回到相应代码确认调用次数、输入规模和具体操作,再分析耗时的根本原因并决定优化方向。
4. 优化后如何正确验证
不要把「启用 cProfile」与「未启用 cProfile」的运行时间直接对比。cProfile 会追踪每次函数调用并引入额外开销,尤其会放大高频 Python 函数的耗时;正确做法是让优化前后都在 cProfile 下、使用相同输入与配置运行。
命令列场景可分别保存两份结果:
# 优化前
python -m cProfile -o baseline.pstats xxx
# 优化后
python -m cProfile -o optimized.pstats xxx
再以 Snakeviz 打开两份结果,对照相同调用链上的 tottime、cumulative time 与端到端耗时:
snakeviz baseline.pstats
snakeviz optimized.pstats
Notebook 场景则对同一个输入分别执行两次 %prun -D。必要时在相同条件下重复多次,排除缓存、首轮加载和系统负载带来的波动。
这个流程可以概括为:先用命令行分析完整链路;需要深入某个函数时再切到 Notebook;通过 Snakeviz 找到热点后回到代码分析原因;完成改动后,以相同的 profiling 条件验证收益。