在前一天(Day 16),我們討論了非同步與 I/O 模型。你可能已經感受到,程式效能問題很少是「神祕的黑盒子」,而是 CPU 與 I/O 的拉扯。接下來,我們要進一步建立「效能觀測」的能力:不是靠直覺亂猜,而是透過工具定位瓶頸。
Python 世界裡,常見的三把利器分別是:
效能問題大致分兩類:
不同問題需要不同工具:
最簡單的用法:
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())
適合情境:
py-spy
不需要改程式碼,就能掛到正在跑的服務。
常用模式:
# 即時觀察類似 top 的函式耗時排名
py-spy top --pid <PID>
# 錄製火焰圖(SVG)
py-spy record -o profile.svg --pid <PID> --duration 30
特色:
小訣竅:錄 15–60 秒就很有代表性;async 程式能直接看出協程堆疊是否在忙,還是卡在 I/O。
用法很簡單,先在目標函式加上 @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
輸出會列出:
適合:
既然前面已經建立了 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 與失效設計」,讓程式能直接跳過重複運算,把效能提升到系統層次。