1. 前言
目標讀者
本文主要針對經常使用 Python 的初學者到中階使用者。對於想要了解並最佳化程式記憶體使用量的讀者尤其有幫助。
文章目的
本篇文章的目的是:
- 理解 Python 記憶體管理的機制。
- 學習測量記憶體使用量的具體方法。
- 掌握降低記憶體使用量的最佳化技巧。
了解這些內容將有助於提升 Python 程式的效能。
2. Python 的記憶體管理基礎
記憶體管理的機制
在 Python 中,記憶體管理主要透過「參考計數(Reference Counting)」與「垃圾回收(Garbage Collection)」這兩種機制來進行。
參考計數
參考計數是指計算每個物件被引用的次數。
在 Python 中,當一個物件被建立時,參考計數會設定為 1。每當其他變數引用該物件時,計數會增加;當引用解除時,計數會減少。當參考計數為 0 時,該物件會自動從記憶體中釋放。
程式碼範例
import sys
a = [1, 2, 3] ## 建立一個 list 物件
print(sys.getrefcount(a)) ## 初始參考計數(通常為 2,包含內部引用)
b = a ## 另一個變數引用相同物件
print(sys.getrefcount(a)) ## 參考計數增加
del b ## 移除引用
print(sys.getrefcount(a)) ## 參考計數減少
垃圾回收
垃圾回收(Garbage Collection, GC)是用來回收無法透過參考計數自動釋放的記憶體,特別是循環引用的情況。Python 內建的垃圾回收器會定期執行,自動清除不再需要的物件。
垃圾回收器擅長處理循環引用的偵測與釋放,在以下情況特別有用:
class Node:
def __init__(self):
self.next = None
## 循環引用範例
a = Node()
b = Node()
a.next = b
b.next = a
## 此情況下參考計數不會歸零,記憶體不會被釋放
若想手動操作垃圾回收器,可使用 gc
模組進行控制:
import gc
## 強制執行垃圾回收
gc.collect()
記憶體洩漏的風險
雖然 Python 的記憶體管理功能強大,但仍非萬無一失。特別是在以下情況中,可能會導致記憶體洩漏的風險:
- 存在循環引用 且垃圾回收功能被停用時。
- 長時間執行的程式中,未使用的物件仍留在記憶體中。
為避免這些問題,應採取避免循環引用的設計,並明確刪除不再需要的物件。
本節小結
- Python 的記憶體管理主要透過「參考計數」與「垃圾回收」兩種機制。
- 垃圾回收對於解決循環引用特別有幫助,但良好的程式設計可有效避免不必要的記憶體消耗。
- 下一節將說明如何實際測量記憶體使用量的方法。
3. 如何確認 Python 的記憶體使用量
基本方法
使用 sys.getsizeof()
確認物件大小
透過 Python 標準函式庫中的 sys
模組提供的 getsizeof()
函數,可以取得任意物件所佔用的記憶體大小(以位元組為單位)。
程式碼範例
import sys
## 查看各種物件的記憶體大小
x = 42
y = [1, 2, 3, 4, 5]
z = {"a": 1, "b": 2}
print(f"x 的大小: {sys.getsizeof(x)} 位元組")
print(f"y 的大小: {sys.getsizeof(y)} 位元組")
print(f"z 的大小: {sys.getsizeof(z)} 位元組")
注意事項
sys.getsizeof()
只能取得該物件本身的大小,不包括其所參考的其他物件(如 list 中的元素)。- 若需精確測量大型物件的實際記憶體使用量,可能需要其他輔助工具。
使用記憶體分析工具
使用 memory_profiler
逐函數測量記憶體
memory_profiler
是一個外部函式庫,可詳細測量 Python 程式中每個函數的記憶體使用情況。能幫助你輕鬆找出記憶體消耗大的部分。
安裝方式
首先,請安裝 memory_profiler
:
pip install memory-profiler
使用方式
使用 @profile
裝飾器可針對函數進行記憶體測量。
from memory_profiler import profile
@profile
def example_function():
a = [i for i in range(10000)]
b = {i: i**2 for i in range(1000)}
return a, b
if __name__ == "__main__":
example_function()
執行程式時,請使用以下指令:
python -m memory_profiler your_script.py
輸出範例
Line ## Mem usage Increment Line Contents
------------------------------------------------
3 13.1 MiB 13.1 MiB @profile
4 16.5 MiB 3.4 MiB a = [i for i in range(10000)]
5 17.2 MiB 0.7 MiB b = {i: i**2 for i in range(1000)}
使用 psutil
監控整體記憶體使用量
psutil
是一個功能強大的函式庫,可監控整個程序的記憶體使用情況,非常適合用於監測整體的資源消耗。
安裝方式
使用以下指令安裝:
pip install psutil
使用方式
import psutil
process = psutil.Process()
print(f"整體程序的記憶體使用量: {process.memory_info().rss / 1024**2:.2f} MB")
主要特色
- 可取得當前程序的記憶體使用量(以位元組為單位)。
- 可用於監控效能並找出最佳化的切入點。
進階記憶體追蹤
使用 tracemalloc
追蹤記憶體配置
使用 Python 標準函式庫的 tracemalloc
模組,可以追蹤記憶體配置來源,分析哪些程式碼區塊使用了最多記憶體。
使用方式
import tracemalloc
## 開始追蹤記憶體
tracemalloc.start()
## 模擬記憶體使用
a = [i for i in range(100000)]
## 顯示記憶體使用狀況
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
print("[記憶體使用情況]")
for stat in top_stats[:5]:
print(stat)
主要用途
- 找出記憶體配置量高的程式區段。
- 比較不同處理流程,找出最佳化空間。
本節小結
- Python 的記憶體使用量可透過多種工具來檢測,從基本的
sys.getsizeof()
到專業的memory_profiler
和psutil
等。 - 若你的程式對效能或資源消耗要求較高,請選擇合適工具來進行精細管理。
- 下一節將介紹如何實際進行記憶體最佳化。
4. 如何最佳化記憶體使用量
選擇高效的資料結構
將 list 替換為 generator
雖然 list comprehension 很方便,但在處理大量資料時會佔用大量記憶體。若改用 generator,則可按需產生資料,大幅降低記憶體使用量。
程式碼範例
## 使用 list
list_data = [i**2 for i in range(1000000)]
print(f"list 記憶體大小: {sys.getsizeof(list_data) / 1024**2:.2f} MB")
## 使用 generator
gen_data = (i**2 for i in range(1000000))
print(f"generator 記憶體大小: {sys.getsizeof(gen_data) / 1024**2:.2f} MB")
透過使用 generator,可以顯著降低記憶體的使用量。
使用 collections.defaultdict
作為字典的替代
雖然 Python 的字典非常好用,但處理大型資料時會消耗較多記憶體。使用 collections.defaultdict
可讓你更有效率地設定預設值,簡化處理流程。
程式碼範例
from collections import defaultdict
## 一般字典
data = {}
data["key"] = data.get("key", 0) + 1
## 使用 defaultdict
default_data = defaultdict(int)
default_data["key"] += 1
管理不再需要的物件
使用 del
明確刪除
在 Python 中,可以手動刪除不再需要的物件,減輕垃圾回收器的負擔。
程式碼範例
## 刪除不再使用的變數
a = [1, 2, 3]
del a
刪除後,變數 a
所佔用的記憶體將被釋放。
使用垃圾回收器
你可以透過 gc
模組來手動執行垃圾回收,以釋放因循環參照而未被自動回收的記憶體。
程式碼範例
import gc
## 執行垃圾回收
gc.collect()
活用外部函式庫進行最佳化
善用 NumPy 與 Pandas
NumPy 和 Pandas 是針對大量數值資料而設計的高效資料處理函式庫,可顯著節省記憶體。
NumPy 使用範例
import numpy as np
## Python list
data_list = [i for i in range(1000000)]
print(f"list 記憶體大小: {sys.getsizeof(data_list) / 1024**2:.2f} MB")
## NumPy 陣列
data_array = np.arange(1000000)
print(f"NumPy 陣列記憶體大小: {data_array.nbytes / 1024**2:.2f} MB")
可以看出,NumPy 陣列比 list 更具記憶體效率。
預防記憶體洩漏
為防止記憶體洩漏,請留意以下幾點:
- 避免循環引用
設計時避免物件互相參考。 - 控制變數作用範圍
注意函式與類別中的變數作用範圍,避免留下不必要的物件。
本節小結
- 若要最佳化記憶體使用量,應選擇高效資料結構,並刪除不再使用的物件。
- 活用如 NumPy、Pandas 等外部函式庫可進一步提升效率。
- 下一節將說明實務上常見問題的解決技巧。
5. 疑難排解(Troubleshooting)
應對記憶體使用量突然飆升的情況
調整垃圾回收器
如果垃圾回收器未正常運作,可能導致不必要的記憶體無法釋放,使記憶體使用量急劇上升。此時可以使用 gc
模組進行調整。
程式碼範例
import gc
## 查看垃圾回收器的觸發門檻值
print(gc.get_threshold())
## 手動執行垃圾回收
gc.collect()
## 調整垃圾回收器設定(例如:變更門檻)
gc.set_threshold(700, 10, 10)
重新檢討物件的生命週期
有時候某些物件即使已不再需要,卻仍留在記憶體中。這時應重新檢視物件的生命週期,並在適當時機將其刪除,以釋放資源。
因循環引用導致的記憶體洩漏
問題概要
當兩個或以上的物件彼此互相引用時,就會形成「循環引用」,這種情況下參考計數無法歸零,可能導致垃圾回收器無法釋放該記憶體。
解決方案
- 使用弱參考(
weakref
模組)來避免循環引用。 - 手動執行垃圾回收以釋放記憶體。
程式碼範例
import weakref
class Node:
def __init__(self, name):
self.name = name
self.next = None
a = Node("A")
b = Node("B")
## 使用弱參考來避免循環引用
a.next = weakref.ref(b)
b.next = weakref.ref(a)
當記憶體分析工具無法運作時
memory_profiler
出現錯誤
有時使用 memory_profiler
時,@profile
裝飾器可能無法正常運作,通常是因為腳本執行方式不正確所導致。
解決方法
- 使用
-m memory_profiler
選項來執行腳本:
python -m memory_profiler your_script.py
- 確認
@profile
有正確套用在目標函數上。
psutil
出現錯誤
如果 psutil
無法正常取得記憶體資訊,可能是版本不兼容或執行環境出現問題。
解決方法
- 確認
psutil
為最新版本,並進行升級:
pip install --upgrade psutil
- 確認你是以正確的方式取得程序資訊:
import psutil
process = psutil.Process()
print(process.memory_info())
應對記憶體不足錯誤(MemoryError)
問題概要
當處理大量資料時,可能會遇到記憶體不足(MemoryError
)的問題。
解決方法
- 減少資料量
刪除不必要的資料,並使用更有效率的資料結構。
## 使用 generator
large_data = (x for x in range(10**8))
- 分段處理
將資料切分為小區塊來處理,可減少一次性佔用的記憶體。
for chunk in range(0, len(data), chunk_size):
process_data(data[chunk:chunk + chunk_size])
- 利用外部儲存
將資料儲存至磁碟而非記憶體(如 SQLite、HDF5 等)。
本節小結
- 透過調整垃圾回收器與物件生命週期管理,可有效控制記憶體使用量。
- 遇到循環引用或工具錯誤時,可使用弱參考或重新設定工具進行修正。
- 面對記憶體不足時,可採用資料壓縮、分段處理或外部儲存等策略來應對。
6. 實用範例:在 Python 腳本中測量記憶體使用量
本節將結合前面介紹的工具與技巧,透過實際的 Python 腳本範例,示範如何分析與最佳化記憶體使用量。透過這些範例,你將學會如何診斷與改善記憶體效能問題。
範例情境:比較 list 與 dict 的記憶體使用量
程式碼範例
下列腳本使用 sys.getsizeof()
與 memory_profiler
來測量 list 與 dict 的記憶體使用情況。
import sys
from memory_profiler import profile
@profile
def compare_memory_usage():
## 建立 list
list_data = [i for i in range(100000)]
print(f"list 的記憶體使用量: {sys.getsizeof(list_data) / 1024**2:.2f} MB")
## 建立 dict
dict_data = {i: i for i in range(100000)}
print(f"dict 的記憶體使用量: {sys.getsizeof(dict_data) / 1024**2:.2f} MB")
return list_data, dict_data
if __name__ == "__main__":
compare_memory_usage()
執行步驟
- 若尚未安裝
memory_profiler
,請先執行以下指令:
pip install memory-profiler
- 使用
memory_profiler
執行腳本:
python -m memory_profiler script_name.py
輸出結果範例
Line ## Mem usage Increment Line Contents
------------------------------------------------
5 13.2 MiB 13.2 MiB @profile
6 17.6 MiB 4.4 MiB list_data = [i for i in range(100000)]
9 22.2 MiB 4.6 MiB dict_data = {i: i for i in range(100000)}
list 的記憶體使用量: 0.76 MB
dict 的記憶體使用量: 3.05 MB
從結果可以看出,dict 的記憶體使用量遠高於 list,這有助於在設計程式時選擇更合適的資料結構。
範例情境:監控整個程序的記憶體使用量
程式碼範例
下列腳本使用 psutil
即時監控整個 Python 程序的記憶體使用情況。
import psutil
import time
def monitor_memory_usage():
process = psutil.Process()
print(f"初始記憶體使用量: {process.memory_info().rss / 1024**2:.2f} MB")
## 模擬記憶體消耗
data = [i for i in range(10000000)]
print(f"處理中記憶體使用量: {process.memory_info().rss / 1024**2:.2f} MB")
del data
time.sleep(2) ## 等待垃圾回收器執行
print(f"刪除資料後記憶體使用量: {process.memory_info().rss / 1024**2:.2f} MB")
if __name__ == "__main__":
monitor_memory_usage()
執行步驟
- 如果尚未安裝
psutil
,請先執行以下指令:
pip install psutil
- 執行該腳本:
python script_name.py
輸出結果範例
初始記憶體使用量: 12.30 MB
處理中記憶體使用量: 382.75 MB
刪除資料後記憶體使用量: 13.00 MB
這個範例展示了大量資料會消耗記憶體,以及在刪除物件後如何釋放記憶體。
本節重點
- 測量記憶體使用量時,可靈活運用
sys.getsizeof()
、memory_profiler
、psutil
等工具。 - 可視化記憶體消耗情況有助於發現瓶頸並進行效能優化。

7. 總結與下一步行動
本文重點整理
- Python 的記憶體管理基礎
- Python 使用「參考計數」與「垃圾回收」兩種機制來自動管理記憶體。
- 為避免循環引用造成的記憶體洩漏,需採取適當的程式設計方式。
- 確認記憶體使用量的方法
- 使用
sys.getsizeof()
可查看單一物件的記憶體大小。 - 使用
memory_profiler
與psutil
等工具可進行函數級與整體程序級的詳細測量。
- 最佳化記憶體使用量的方法
- 使用 generator 或高效資料結構(例如 NumPy 陣列)可降低記憶體消耗。
- 刪除不再使用的物件與適時使用垃圾回收器有助於防止記憶體洩漏。
- 實務應用範例
- 透過實際的程式碼範例,我們學習了如何測量與分析記憶體使用狀況。
- 比較 list 與 dict 的記憶體差異、監控整體記憶體使用量等技巧都可直接應用於開發中。
下一步行動
- 實際應用於你的專案
- 將本文介紹的技巧與工具整合至你日常的 Python 專案中。
- 例如,在處理大量資料的腳本中使用
memory_profiler
來找出記憶體使用過高的區段。
- 進一步學習進階記憶體管理
- Python 官方文件中有更深入的資源,可參考以下連結進一步學習:
- 善用外部工具與服務
- 在大型專案中,可考慮使用
py-spy
或PyCharm
的 profiler 工具進行更深入的分析。 - 在雲端環境下執行程式時,也可以整合 AWS、Google Cloud 等平台所提供的資源監控工具。
- 持續進行程式碼檢討與改進
- 若你是團隊開發的一員,可在 code review 中納入記憶體效能的討論,發掘潛在的優化點。
- 培養注重記憶體效率的開發習慣,將為長期開發效能帶來正面影響。
結語
妥善管理 Python 程式的記憶體使用,不僅能提升效能,也代表開發者具備更高層次的工程實力。希望你能透過本篇文章的內容,實際應用於專案中,並持續深化理解與技巧,讓你的程式更加高效且穩定!