tracemalloc
— 跟蹤記憶體分配¶
3.4 版本新增。
原始碼: Lib/tracemalloc.py
tracemalloc 模組是一個除錯工具,用於跟蹤 Python 分配的記憶體塊。它提供以下資訊
物件被分配的回溯
按檔名和行號統計已分配的記憶體塊:已分配的記憶體塊的總大小、數量和平均大小
計算兩個快照之間的差異以檢測記憶體洩漏
要跟蹤 Python 分配的大多數記憶體塊,應儘早啟動該模組,方法是將 PYTHONTRACEMALLOC
環境變數設定為 1
,或使用 -X
tracemalloc
命令列選項。可以在執行時呼叫 tracemalloc.start()
函式以開始跟蹤 Python 記憶體分配。
預設情況下,已分配的記憶體塊的跟蹤僅儲存最近的幀(1 幀)。要在啟動時儲存 25 個幀:將 PYTHONTRACEMALLOC
環境變數設定為 25
,或使用 -X
tracemalloc=25
命令列選項。
示例¶
顯示前 10¶
顯示分配最多記憶體的 10 個檔案
import tracemalloc
tracemalloc.start()
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 ]")
for stat in top_stats[:10]:
print(stat)
Python 測試套件的輸出示例
[ Top 10 ]
<frozen importlib._bootstrap>:716: size=4855 KiB, count=39328, average=126 B
<frozen importlib._bootstrap>:284: size=521 KiB, count=3199, average=167 B
/usr/lib/python3.4/collections/__init__.py:368: size=244 KiB, count=2315, average=108 B
/usr/lib/python3.4/unittest/case.py:381: size=185 KiB, count=779, average=243 B
/usr/lib/python3.4/unittest/case.py:402: size=154 KiB, count=378, average=416 B
/usr/lib/python3.4/abc.py:133: size=88.7 KiB, count=347, average=262 B
<frozen importlib._bootstrap>:1446: size=70.4 KiB, count=911, average=79 B
<frozen importlib._bootstrap>:1454: size=52.0 KiB, count=25, average=2131 B
<string>:5: size=49.7 KiB, count=148, average=344 B
/usr/lib/python3.4/sysconfig.py:411: size=48.0 KiB, count=1, average=48.0 KiB
我們可以看到 Python 從模組載入了 4855 KiB
的資料(位元組碼和常量),並且 collections
模組分配了 244 KiB
來構建 namedtuple
型別。
有關更多選項,請參閱 Snapshot.statistics()
。
計算差異¶
拍攝兩個快照並顯示差異
import tracemalloc
tracemalloc.start()
# ... start your application ...
snapshot1 = tracemalloc.take_snapshot()
# ... call the function leaking memory ...
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
print("[ Top 10 differences ]")
for stat in top_stats[:10]:
print(stat)
在執行 Python 測試套件的某些測試之前/之後輸出的示例
[ Top 10 differences ]
<frozen importlib._bootstrap>:716: size=8173 KiB (+4428 KiB), count=71332 (+39369), average=117 B
/usr/lib/python3.4/linecache.py:127: size=940 KiB (+940 KiB), count=8106 (+8106), average=119 B
/usr/lib/python3.4/unittest/case.py:571: size=298 KiB (+298 KiB), count=589 (+589), average=519 B
<frozen importlib._bootstrap>:284: size=1005 KiB (+166 KiB), count=7423 (+1526), average=139 B
/usr/lib/python3.4/mimetypes.py:217: size=112 KiB (+112 KiB), count=1334 (+1334), average=86 B
/usr/lib/python3.4/http/server.py:848: size=96.0 KiB (+96.0 KiB), count=1 (+1), average=96.0 KiB
/usr/lib/python3.4/inspect.py:1465: size=83.5 KiB (+83.5 KiB), count=109 (+109), average=784 B
/usr/lib/python3.4/unittest/mock.py:491: size=77.7 KiB (+77.7 KiB), count=143 (+143), average=557 B
/usr/lib/python3.4/urllib/parse.py:476: size=71.8 KiB (+71.8 KiB), count=969 (+969), average=76 B
/usr/lib/python3.4/contextlib.py:38: size=67.2 KiB (+67.2 KiB), count=126 (+126), average=546 B
我們可以看到 Python 已載入 8173 KiB
的模組資料(位元組碼和常量),這比在測試之前(當拍攝上一個快照時)載入的資料多 4428 KiB
。 同樣,linecache
模組快取了 940 KiB
的 Python 原始碼來格式化回溯,所有這些都是自上一個快照以來的資料。
如果系統可用記憶體較少,可以使用 Snapshot.dump()
方法將快照寫入磁碟,以離線分析快照。然後使用 Snapshot.load()
方法重新載入快照。
獲取記憶體塊的回溯¶
顯示最大記憶體塊的回溯的程式碼
import tracemalloc
# Store 25 frames
tracemalloc.start(25)
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('traceback')
# pick the biggest memory block
stat = top_stats[0]
print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))
for line in stat.traceback.format():
print(line)
Python 測試套件的輸出示例(回溯限制為 25 幀)
903 memory blocks: 870.1 KiB
File "<frozen importlib._bootstrap>", line 716
File "<frozen importlib._bootstrap>", line 1036
File "<frozen importlib._bootstrap>", line 934
File "<frozen importlib._bootstrap>", line 1068
File "<frozen importlib._bootstrap>", line 619
File "<frozen importlib._bootstrap>", line 1581
File "<frozen importlib._bootstrap>", line 1614
File "/usr/lib/python3.4/doctest.py", line 101
import pdb
File "<frozen importlib._bootstrap>", line 284
File "<frozen importlib._bootstrap>", line 938
File "<frozen importlib._bootstrap>", line 1068
File "<frozen importlib._bootstrap>", line 619
File "<frozen importlib._bootstrap>", line 1581
File "<frozen importlib._bootstrap>", line 1614
File "/usr/lib/python3.4/test/support/__init__.py", line 1728
import doctest
File "/usr/lib/python3.4/test/test_pickletools.py", line 21
support.run_doctest(pickletools)
File "/usr/lib/python3.4/test/regrtest.py", line 1276
test_runner()
File "/usr/lib/python3.4/test/regrtest.py", line 976
display_failure=not verbose)
File "/usr/lib/python3.4/test/regrtest.py", line 761
match_tests=ns.match_tests)
File "/usr/lib/python3.4/test/regrtest.py", line 1563
main()
File "/usr/lib/python3.4/test/__main__.py", line 3
regrtest.main_in_temp_cwd()
File "/usr/lib/python3.4/runpy.py", line 73
exec(code, run_globals)
File "/usr/lib/python3.4/runpy.py", line 160
"__main__", fname, loader, pkg_name)
我們可以看到,大部分記憶體是在 importlib
模組中分配的,用於從模組載入資料(位元組碼和常量):870.1 KiB
。 回溯是 importlib
最近載入資料的位置:在 doctest
模組的 import pdb
行上。 如果載入新模組,則回溯可能會更改。
漂亮的 top¶
用於顯示分配最多記憶體的 10 行程式碼的程式碼,並具有漂亮的輸出,忽略 <frozen importlib._bootstrap>
和 <unknown>
檔案
import linecache
import os
import tracemalloc
def display_top(snapshot, key_type='lineno', limit=10):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "<unknown>"),
))
top_stats = snapshot.statistics(key_type)
print("Top %s lines" % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
print("#%s: %s:%s: %.1f KiB"
% (index, frame.filename, frame.lineno, stat.size / 1024))
line = linecache.getline(frame.filename, frame.lineno).strip()
if line:
print(' %s' % line)
other = top_stats[limit:]
if other:
size = sum(stat.size for stat in other)
print("%s other: %.1f KiB" % (len(other), size / 1024))
total = sum(stat.size for stat in top_stats)
print("Total allocated size: %.1f KiB" % (total / 1024))
tracemalloc.start()
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)
Python 測試套件的輸出示例
Top 10 lines
#1: Lib/base64.py:414: 419.8 KiB
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
#2: Lib/base64.py:306: 419.8 KiB
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
#3: collections/__init__.py:368: 293.6 KiB
exec(class_definition, namespace)
#4: Lib/abc.py:133: 115.2 KiB
cls = super().__new__(mcls, name, bases, namespace)
#5: unittest/case.py:574: 103.1 KiB
testMethod()
#6: Lib/linecache.py:127: 95.4 KiB
lines = fp.readlines()
#7: urllib/parse.py:476: 71.8 KiB
for a in _hexdig for b in _hexdig}
#8: <string>:5: 62.0 KiB
#9: Lib/_weakrefset.py:37: 60.0 KiB
self.data = set()
#10: Lib/base64.py:142: 59.8 KiB
_b32tab2 = [a + b for a in _b32tab for b in _b32tab]
6220 other: 3602.8 KiB
Total allocated size: 5303.1 KiB
有關更多選項,請參閱 Snapshot.statistics()
。
記錄所有跟蹤的記憶體塊的當前和峰值大小¶
以下程式碼透過建立一個數字列表來低效地計算兩個和,例如 0 + 1 + 2 + ...
。 此列表會暫時消耗大量記憶體。 我們可以使用 get_traced_memory()
和 reset_peak()
來觀察計算總和後的小記憶體使用情況,以及計算期間的峰值記憶體使用情況
import tracemalloc
tracemalloc.start()
# Example code: compute a sum with a large temporary list
large_sum = sum(list(range(100000)))
first_size, first_peak = tracemalloc.get_traced_memory()
tracemalloc.reset_peak()
# Example code: compute a sum with a small temporary list
small_sum = sum(list(range(1000)))
second_size, second_peak = tracemalloc.get_traced_memory()
print(f"{first_size=}, {first_peak=}")
print(f"{second_size=}, {second_peak=}")
輸出
first_size=664, first_peak=3592984
second_size=804, second_peak=29704
使用 reset_peak()
確保我們能夠準確記錄計算 small_sum
期間的峰值,即使它比自呼叫 start()
以來的記憶體塊總峰值大小小得多。 如果不呼叫 reset_peak()
,second_peak
仍然是計算 large_sum
的峰值(即等於 first_peak
)。 在這種情況下,兩個峰值都遠高於最終的記憶體使用量,這表明我們可以最佳化(透過刪除對 list
的不必要呼叫,並寫入 sum(range(...))
)。
API¶
函式¶
- tracemalloc.get_object_traceback(obj)¶
獲取 Python 物件 obj 被分配的回溯。返回一個
Traceback
例項,如果tracemalloc
模組未跟蹤記憶體分配或未跟蹤物件的分配,則返回None
。另請參閱
gc.get_referrers()
和sys.getsizeof()
函式。
- tracemalloc.get_traceback_limit()¶
獲取跟蹤中儲存的最大幀數。
必須先使用
tracemalloc
模組跟蹤記憶體分配才能獲取限制,否則會引發異常。限制由
start()
函式設定。
- tracemalloc.get_traced_memory()¶
獲取
tracemalloc
模組跟蹤的記憶體塊的當前大小和峰值大小,以元組形式返回:(current: int, peak: int)
。
- tracemalloc.reset_peak()¶
將
tracemalloc
模組跟蹤的記憶體塊的峰值大小設定為當前大小。如果
tracemalloc
模組沒有跟蹤記憶體分配,則不執行任何操作。此函式僅修改記錄的峰值大小,並且不會像
clear_traces()
那樣修改或清除任何跟蹤資訊。在呼叫reset_peak()
之前使用take_snapshot()
獲取的快照可以有意義地與呼叫後獲取的快照進行比較。另請參閱
get_traced_memory()
。3.9 版本中新增。
- tracemalloc.get_tracemalloc_memory()¶
獲取
tracemalloc
模組用於儲存記憶體塊跟蹤資訊的記憶體使用量(以位元組為單位)。返回一個int
。
- tracemalloc.is_tracing()¶
如果
tracemalloc
模組正在跟蹤 Python 記憶體分配,則返回True
;否則返回False
。
- tracemalloc.start(nframe: int = 1)¶
開始跟蹤 Python 記憶體分配:在 Python 記憶體分配器上安裝鉤子。收集的跟蹤的回溯將限制為 *nframe* 幀。預設情況下,記憶體塊的跟蹤僅儲存最近的幀:限制為
1
。 *nframe* 必須大於或等於1
。您仍然可以透過檢視
Traceback.total_nframe
屬性來讀取組成回溯的總幀數的原始數量。儲存多於
1
幀僅對於計算按'traceback'
分組的統計資訊或計算累積統計資訊有用:請參閱Snapshot.compare_to()
和Snapshot.statistics()
方法。儲存更多幀會增加
tracemalloc
模組的記憶體和 CPU 開銷。使用get_tracemalloc_memory()
函式來衡量tracemalloc
模組使用了多少記憶體。PYTHONTRACEMALLOC
環境變數(PYTHONTRACEMALLOC=NFRAME
)和-X
tracemalloc=NFRAME
命令列選項可用於在啟動時開始跟蹤。另請參閱
stop()
,is_tracing()
和get_traceback_limit()
函式。
- tracemalloc.stop()¶
停止跟蹤 Python 記憶體分配:解除安裝 Python 記憶體分配器上的鉤子。還會清除之前收集的所有由 Python 分配的記憶體塊的跟蹤資訊。
在清除它們之前,呼叫
take_snapshot()
函式來獲取跟蹤的快照。另請參閱
start()
,is_tracing()
和clear_traces()
函式。
- tracemalloc.take_snapshot()¶
獲取由 Python 分配的記憶體塊的跟蹤快照。返回一個新的
Snapshot
例項。快照不包括在
tracemalloc
模組開始跟蹤記憶體分配之前分配的記憶體塊。跟蹤的回溯限制為
get_traceback_limit()
幀。使用start()
函式的 *nframe* 引數來儲存更多幀。必須先使用
tracemalloc
模組跟蹤記憶體分配才能獲取快照,請參閱start()
函式。另請參閱
get_object_traceback()
函式。
DomainFilter¶
過濾器¶
- class tracemalloc.Filter(inclusive: bool, filename_pattern: str, lineno: int = None, all_frames: bool = False, domain: int = None)¶
根據記憶體塊的跟蹤記錄進行篩選。
關於 filename_pattern 的語法,請參閱
fnmatch.fnmatch()
函式。'.pyc'
副檔名會被替換為'.py'
。示例
Filter(True, subprocess.__file__)
只包含subprocess
模組的跟蹤記錄Filter(False, tracemalloc.__file__)
排除tracemalloc
模組的跟蹤記錄Filter(False, "<unknown>")
排除空的追溯
在 3.5 版本中更改:
'.pyo'
副檔名不再被替換為'.py'
。在 3.6 版本中更改: 添加了
domain
屬性。- domain¶
記憶體塊的地址空間 (
int
或None
)。tracemalloc 使用域
0
來跟蹤 Python 所做的記憶體分配。C 擴充套件可以使用其他域來跟蹤其他資源。
- inclusive¶
如果 inclusive 為
True
(包含),則僅匹配在檔名與filename_pattern
匹配的檔案中,在行號lineno
處分配的記憶體塊。如果 inclusive 為
False
(排除),則忽略在檔名與filename_pattern
匹配的檔案中,在行號lineno
處分配的記憶體塊。
- lineno¶
過濾器的行號 (
int
)。如果 lineno 為None
,則過濾器匹配任何行號。
- filename_pattern¶
過濾器的檔名模式 (
str
)。只讀屬性。
- all_frames¶
如果 all_frames 為
True
,則檢查回溯的所有幀。如果 all_frames 為False
,則只檢查最近的幀。如果回溯限制為
1
,則此屬性無效。請參閱get_traceback_limit()
函式和Snapshot.traceback_limit
屬性。
幀¶
快照¶
- class tracemalloc.Snapshot¶
由 Python 分配的記憶體塊的跟蹤快照。
take_snapshot()
函式建立一個快照例項。- compare_to(old_snapshot: Snapshot, key_type: str, cumulative: bool = False)¶
計算與舊快照的差異。獲取按 key_type 分組的
StatisticDiff
例項的排序列表形式的統計資訊。關於 key_type 和 cumulative 引數,請參閱
Snapshot.statistics()
方法。結果從大到小排序,排序依據:
StatisticDiff.size_diff
的絕對值、StatisticDiff.size
、StatisticDiff.count_diff
的絕對值、Statistic.count
,然後是StatisticDiff.traceback
。
- filter_traces(filters)¶
建立一個新的
Snapshot
例項,其中包含經過篩選的traces
序列。filters 是一個DomainFilter
和Filter
例項的列表。如果 filters 是一個空列表,則返回一個新的Snapshot
例項,其中包含 traces 的副本。所有包含性過濾器會一次性應用,如果某個 trace 不符合任何包含性過濾器,則會被忽略。如果至少有一個排除性過濾器匹配某個 trace,則該 trace 會被忽略。
3.6 版本更改: 現在 filters 中也接受
DomainFilter
例項。
- statistics(key_type: str, cumulative: bool = False)¶
獲取按 key_type 分組的
Statistic
例項的排序列表形式的統計資訊。key_type
描述
'filename'
filename
'lineno'
檔名和行號
'traceback'
traceback
如果 cumulative 為
True
,則累加 trace 的回溯中所有幀的記憶體塊大小和計數,而不僅僅是最新的幀。累積模式只能與 key_type 等於'filename'
和'lineno'
一起使用。結果按照以下順序從大到小排序:
Statistic.size
、Statistic.count
,然後是Statistic.traceback
。
- traceback_limit¶
儲存在
traces
的回溯中的最大幀數:當拍攝快照時get_traceback_limit()
的結果。
- traces¶
Python 分配的所有記憶體塊的跟蹤記錄:
Trace
例項的序列。該序列的順序未定義。使用
Snapshot.statistics()
方法獲取排序的統計資訊列表。
Statistic¶
- class tracemalloc.Statistic¶
記憶體分配的統計資訊。
Snapshot.statistics()
返回一個Statistic
例項的列表。另請參閱
StatisticDiff
類。- count¶
記憶體塊的數量(
int
)。
- size¶
記憶體塊的總大小(以位元組為單位)(
int
)。
StatisticDiff¶
- class tracemalloc.StatisticDiff¶
舊快照例項和新快照例項之間記憶體分配的統計差異。
Snapshot.compare_to()
返回StatisticDiff
例項的列表。另請參閱Statistic
類。- count¶
新快照中的記憶體塊數量(
int
):如果記憶體塊在新快照中已釋放,則為0
。
- count_diff¶
舊快照和新快照之間記憶體塊數量的差異(
int
):如果記憶體塊在新快照中已分配,則為0
。
- size¶
新快照中記憶體塊的總大小(以位元組為單位)(
int
):如果記憶體塊在新快照中已釋放,則為0
。
- size_diff¶
舊快照和新快照之間記憶體塊總大小的差異(以位元組為單位)(
int
):如果記憶體塊在新快照中已分配,則為0
。
Trace¶
回溯¶
- class tracemalloc.Traceback¶
Frame
例項的序列,從最舊的幀排序到最近的幀。回溯至少包含
1
個幀。如果tracemalloc
模組無法獲取幀,則使用檔名為"<unknown>"
,行號為0
。當獲取快照時,回溯的幀數限制為
get_traceback_limit()
個。請參閱take_snapshot()
函式。回溯的原始幀數儲存在Traceback.total_nframe
屬性中。這可以用來判斷回溯是否已被回溯限制截斷。Trace.traceback
屬性是Traceback
例項的例項。在 3.7 版本中更改: 幀現在從最舊的到最近的排序,而不是從最近的到最舊的排序。
- total_nframe¶
截斷前組成回溯的總幀數。如果資訊不可用,此屬性可以設定為
None
。
在 3.9 版本中更改: 添加了
Traceback.total_nframe
屬性。- format(limit=None, most_recent_first=False)¶
將回溯格式化為行列表。使用
linecache
模組從原始碼中檢索行。如果設定了 limit,則格式化 limit 個最近的幀(如果 limit 為正數)。否則,格式化abs(limit)
個最舊的幀。如果 most_recent_first 為True
,則格式化幀的順序將反轉,首先返回最近的幀,而不是最後返回。類似於
traceback.format_tb()
函式,除了format()
不包含換行符。示例
print("Traceback (most recent call first):") for line in traceback: print(line)
輸出
Traceback (most recent call first): File "test.py", line 9 obj = Object() File "test.py", line 12 tb = tracemalloc.get_object_traceback(f())