所有文章

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函數自身耗時,不含子函數。
第一個 percalltottime / ncalls,即函數自身的平均每次耗時。
cumtime函數及其子函數的累積耗時。
第二個 percallcumtime / 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 條件驗證收益。