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 條件驗證收益。