所有文章

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 打开两份结果,对照相同调用链上的 tottimecumulative time 与端到端耗时:

snakeviz baseline.pstats
snakeviz optimized.pstats

Notebook 场景则对同一个输入分别执行两次 %prun -D。必要时在相同条件下重复多次,排除缓存、首轮加载和系统负载带来的波动。

这个流程可以概括为:先用命令行分析完整链路;需要深入某个函数时再切到 Notebook;通过 Snakeviz 找到热点后回到代码分析原因;完成改动后,以相同的 profiling 条件验证收益。