iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0

在前一天(Day 16),我們討論了非同步與 I/O 模型。你可能已經感受到,程式效能問題很少是「神祕的黑盒子」,而是 CPU 與 I/O 的拉扯。接下來,我們要進一步建立「效能觀測」的能力:不是靠直覺亂猜,而是透過工具定位瓶頸。

Python 世界裡,常見的三把利器分別是:

  • cProfile:標準庫自帶的剖析器,適合整體鳥瞰。
  • py-spy:低干擾的取樣式剖析器,能掛到線上程式並輸出火焰圖。
  • line-profiler:逐行耗時分析,特別適合鎖定內層熱點。

為什麼要量測,而不是猜測?

效能問題大致分兩類:

  1. CPU 密集:演算法複雜或純 Python 迴圈吃滿單核。
  2. I/O 密集:程式在等待資料庫、API、檔案或網路,CPU 在發呆。

不同問題需要不同工具:

  • 要知道哪個函式在燒 CPU,就用 cProfile 或 py-spy。
  • 要深入每一行程式碼,line-profiler 才能逼出真相。
  • 如果是 I/O 在拖慢,那就回頭檢查非同步設計是否正確。

cProfile:標準庫的全域鳥瞰

最簡單的用法:

python -m cProfile -o profile.out your_app.py
python -m pstats profile.out

在互動式 pstats 裡,可以用:

stats.sort_stats("tottime").print_stats(30)  # 純 Python 執行時間
stats.sort_stats("cumtime").print_stats(30)  # 累積含呼叫鏈時間

也能嵌入程式中:

import cProfile, pstats, io

pr = cProfile.Profile()
pr.enable()

# ... 你的程式邏輯 ...

pr.disable()
s = io.StringIO()
pstats.Stats(pr, stream=s).sort_stats("cumtime").print_stats(40)
print(s.getvalue())

適合情境:

  • 想比較優化前後的差異。
  • 想知道呼叫關係,整體輪廓。
  • 能搭配 CI 基準測試,留存剖析紀錄。

py-spy:線上火焰圖,低干擾取樣

py-spy 不需要改程式碼,就能掛到正在跑的服務。

常用模式:

# 即時觀察類似 top 的函式耗時排名
py-spy top --pid <PID>

# 錄製火焰圖(SVG)
py-spy record -o profile.svg --pid <PID> --duration 30

特色:

  • 取樣式,不會大幅拖慢程式。
  • 火焰圖能一眼看到哪個堆疊最寬、最耗 CPU。
  • 適合線上 debug,特別是突發卡頓或高 CPU 時。

小訣竅:錄 15–60 秒就很有代表性;async 程式能直接看出協程堆疊是否在忙,還是卡在 I/O。


line-profiler:逐行剖析熱點

用法很簡單,先在目標函式加上 @profile

@profile
def tight_loop(a, b):
    s = 0
    for i in range(a):
        s += (i * b) % 7
    return s

然後用 kernprof 執行:

kernprof -l -v your_script.py

輸出會列出:

  • 每行執行次數
  • 平均耗時
  • 總耗時

適合:

  • 你已經透過 cProfile/py-spy 找到「哪個函式」是瓶頸。
  • 需要進一步確認「哪一行」最拖慢。
  • 特別適合數值處理、文字處理的內層迴圈。

工程化整合:Hatch × Nox

既然前面已經建立了 Hatch 與 Nox 的工作流,效能觀測也能一鍵化。

pyproject.toml 裡:

[project.optional-dependencies]
perf = [
  "py-spy>=0.3",
  "line-profiler>=4.1",
  "snakeviz>=2.2"
]

[tool.hatch.envs.perf]
dependencies = [".[perf]"]

[tool.hatch.envs.perf.scripts]
cprof = "python -m cProfile -o .perf/cprof.out -m your_app && snakeviz .perf/cprof.out"
pyspy = "py-spy record -o .perf/pyspy.svg --pid $(pgrep -n -f 'your_app') --duration 30 && echo '=> .perf/pyspy.svg'"
lprof = "kernprof -l -v scripts/bench.py"

noxfile.py 裡:

import nox

@nox.session(name="perf-cprof", venv_backend="none")
def perf_cprof(session):
    session.run("hatch", "run", "perf:cprof")

@nox.session(name="perf-pyspy", venv_backend="none")
def perf_pyspy(session):
    session.run("hatch", "run", "perf:pyspy")

@nox.session(name="perf-line", venv_backend="none")
def perf_line(session):
    session.run("hatch", "run", "perf:lprof")

這樣就能保持跟 lint、test 一樣的一鍵體驗。



結語

效能優化沒有魔法,只有測量。先用 cProfile 找方向,再用 py-spy 看線上真相,最後用 line-profiler 精修。別忘了,很多時候省下的不是 CPU,而是別讓 CPU傻等 I/O。

下一篇(Day 18),我們會談「快取策略:lru_cache、Redis 與失效設計」,讓程式能直接跳過重複運算,把效能提升到系統層次。


上一篇
Day 16 -非同步基礎:asyncio / anyio 的落地心法
下一篇
Day 18 -快取策略:lru_cache、Redis 與失效設計
系列文
30 天 Python 專案工坊:環境、結構、測試到部署全打通23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言