Python 效能優化與分析:cProfile 與 memory_profiler (第 35 章)

站主自己的課程,請大家支持
揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站? 揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站?
  • Post by
  • Jan 18, 2024
post-thumb

許多開發者在遇到程式慢時,會憑「感覺」去優化程式碼。 「我覺得這裡迴圈太多了,改一下好了。」 「我覺得用這個函式比較慢…」

請停止猜測 (Stop Guessing)! 效能優化必須基於數據。Profiling (效能分析) 就是幫你的程式做全身健康檢查,精確指出哪一行程式碼或是哪一個函式拖慢了整體速度。

1. 內建 CPU 分析器:cProfile

Python 內建的 cProfile 是最常用的工具,它屬於 Deterministic Profiler,會記錄所有函式的呼叫次數與執行時間。

命令行使用

python -m cProfile -s tottime your_script.py
  • -s tottime: 依據 Total Time (總執行時間) 排序。

你會看到類似這樣的輸出:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.234    1.234 your_script.py:10(slow_func)
    10000    0.500    0.000    0.500    0.000 {method 'append' of 'list' objects}
  • ncalls: 呼叫次數。
  • tottime: 該函式本身的執行時間 (不含子函式)。這通常是你要優化的地方
  • cumtime: 該函式加上子函式的總時間。

程式碼中使用

import cProfile
import pstats

def my_slow_function():
    ...

with cProfile.Profile() as pr:
    my_slow_function()

stats = pstats.Stats(pr)
stats.sort_stats('tottime').print_stats(10) # 印出前 10 名

2. 記憶體分析器:memory_profiler

有時候程式不是慢,是吃爆了記憶體 (OOM, Out Of Memory)memory_profiler 可以逐行分析記憶體使用量。

安裝: pip install memory_profiler

使用方法

在你想分析的函式前加上 @profile 裝飾器 (不需要 import 任何東西,這是 memory_profiler 特有的魔術)。

# test_memory.py

@profile
def heavy_memory_func():
    a = [1] * (10 ** 6) # 約 8MB
    b = [2] * (2 * 10 ** 7) # 約 160MB
    del b
    return a

if __name__ == '__main__':
    heavy_memory_func()

執行時使用 mprof run 或直接用 python:

python -m memory_profiler test_memory.py

輸出結果:

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     3     38.0 MiB     38.0 MiB           1   @profile
     4                                         def heavy_memory_func():
     5     45.7 MiB      7.6 MiB           1       a = [1] * (10 ** 6)
     6    198.3 MiB    152.6 MiB           1       b = [2] * (2 * 10 ** 7)
     7     45.7 MiB   -152.6 MiB           1       del b
     8     45.7 MiB      0.0 MiB           1       return a

你可以清楚看到哪一行程式碼瞬間吃掉了 152 MB 記憶體!

3. line_profiler:逐行時間分析

cProfile 只能看到函式層級,如果你想知道函式內部哪一行最慢,就要用 line_profiler

安裝:pip install line_profiler 使用方式與 memory_profiler 類似,加 @profile 並用 kernprof -l -v your_script.py 執行。

4. 優化策略

找到瓶頸後,怎麼優化?

  1. 演算法優化:O(n^2) 改成 O(n) 或 O(log n)。(例如用 set 查找代替 list)。
  2. 資料結構選擇:用 deque 代替 list (如果你常在開頭插入),用 ndarray 代替迴圈。
  3. 快取 (Caching):使用 functools.lru_cache 快取運算結果。
  4. 換工具:改用 C Extension (如 NumPy, Cython) 甚至 Rust。

5. Batch 7 總結

本批次我們深入探討了軟體工程的核心:

  • Ch 31: SOLID 原則 (架構)
  • Ch 32: Design Patterns II (模式)
  • Ch 33: Pytest (測試)
  • Ch 34: Logging & Debugging (除錯)
  • Ch 35: Profiling (效能)

現在你的程式碼不僅僅是能跑,而且是結構良好、經過測試、容易除錯且效能優異的。

下一階段 Batch 8 (Advanced Testing & Deployment),我們將挑戰更困難的測試場景 (Mocking) 與自動化部署 (CI/CD)!(但首先,我們會把之前欠的圖片補上 😉)


延伸閱讀

LATEST POST
TAG