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
行。如果載入新模組,追溯可能會發生變化。
漂亮的前 10 個¶
顯示分配最多記憶體的 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()
那樣修改或清除任何跟蹤。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¶
Filter¶
- 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
屬性。
Frame¶
Snapshot¶
- class tracemalloc.Snapshot¶
Python 分配的記憶體塊的跟蹤快照。
take_snapshot()
函式建立一個快照例項。- compare_to(old_snapshot: Snapshot, key_type: str, cumulative: bool = False)¶
與舊快照計算差異。以
StatisticDiff
例項的排序列表形式獲取統計資訊,按 key_type 分組。有關 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
例項。所有包含性過濾器同時應用,如果沒有包含性過濾器匹配跟蹤,則忽略該跟蹤。如果至少有一個排他性過濾器匹配跟蹤,則忽略該跟蹤。
3.6 版本中更改: 現在 filters 也接受
DomainFilter
例項。
- statistics(key_type: str, cumulative: bool = False)¶
獲取以
Statistic
例項的排序列表形式的統計資訊,按 key_type 分組key_type
description
'filename'
filename
'lineno'
檔名和行號
'traceback'
traceback
如果 cumulative 為
True
,則累加跟蹤的追溯中所有幀(而不僅僅是最近的幀)的記憶體塊大小和數量。累積模式只能與 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
例項之間記憶體分配的統計差異。Snapshot.compare_to()
返回一個StatisticDiff
例項列表。另請參閱Statistic
類。- count¶
新快照中的記憶體塊數量(
int
):如果新快照中記憶體塊已釋放,則為0
。
- count_diff¶
舊快照和新快照之間記憶體塊數量的差異(
int
):如果新快照中記憶體塊已分配,則為0
。
- size¶
新快照中記憶體塊的總大小(以位元組為單位)(
int
):如果新快照中記憶體塊已釋放,則為0
。
- size_diff¶
舊快照和新快照之間記憶體塊總大小(以位元組為單位)的差異(
int
):如果新快照中記憶體塊已分配,則為0
。
Trace¶
Traceback¶
- 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())