tracemalloc 是Python3.4以后,新增加的功能。
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
最近加载的数据:在 import pdb
直线 doctest
模块。如果加载了新模块,则回溯可能会更改。
漂亮的陀螺
显示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.
clear_traces
()
清除python分配的内存块的痕迹。
也见 stop()
.tracemalloc.
get_object_traceback
(obj)
获取python对象的回溯 obj 已分配。返回A Traceback
实例,或 None
如果 tracemalloc
模块未跟踪内存分配或未跟踪对象的分配。
也见 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()
在呼叫之前 reset_peak()
可以有意义地与通话后拍摄的快照进行比较。
也见 get_traced_memory()
.
3.9 新版功能.tracemalloc.
get_tracemalloc_memory
()
获取 tracemalloc
用于存储内存块痕迹的模块。返回一 int
.tracemalloc.
is_tracing
()
True
如果 tracemalloc
模块正在跟踪python内存分配, False
否则。
也见 start()
和 stop()
功能。tracemalloc.
start
(nframe: int = 1)
开始跟踪python内存分配:在python内存分配器上安装钩子。收集的痕迹追踪将限于 nFrice 框架。默认情况下,内存块的跟踪只存储最新的帧:限制为 1
. nFrice 必须大于或等于 1
.
通过查看 Traceback.total_nframe
属性。
存储超过 1
框架仅用于计算按分组的统计信息 'traceback'
或者计算累积统计:请参见 Snapshot.compare_to()
和 Snapshot.statistics()
方法。
存储更多的帧会增加 tracemalloc
模块。使用 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()
框架。使用 nFrice 的参数 start()
函数来存储更多帧。
这个 tracemalloc
模块必须跟踪内存分配才能获取快照,请参见 start()
功能。
也见 get_object_traceback()
功能。
DomainFilter
class tracemalloc.
DomainFilter
(inclusive: bool, domain: int)
按地址空间(域)筛选内存块的跟踪。
3.6 新版功能.inclusive
如果 包容的 是 True
(include),匹配地址空间中分配的内存块 domain
.
如果 包容的 是 False
(排除),匹配地址空间中未分配的内存块 domain
.domain
内存块的地址空间 (int
)只读属性。
滤波器
class tracemalloc.
Filter
(inclusive: bool, filename_pattern: str, lineno: int = None, all_frames: bool = False, domain: int = None)
过滤内存块的痕迹。
见 fnmatch.fnmatch()
函数的语法 filename_pattern . 这个 '.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
如果 包容的 是 True
(include),仅匹配在名称匹配的文件中分配的内存块 filename_pattern
在行号处 lineno
.
如果 包容的 是 False
(排除),忽略在名称匹配的文件中分配的内存块 filename_pattern
在行号处 lineno
.lineno
行号 (int
)过滤器的。如果 林诺 是 None
,筛选器匹配任何行号。filename_pattern
筛选器的文件名模式 (str
)只读属性。all_frames
如果 all_frames 是 True
,将检查回溯的所有帧。如果 all_frames 是 False
,仅选中最近的帧。
如果回溯限制为 1
. 见 get_traceback_limit()
功能和 Snapshot.traceback_limit
属性。
框架
class tracemalloc.
Frame
回溯的框架。
这个 Traceback
类是一个序列 Frame
实例。filename
文件名 (str
)lineno
行号 (int
)
快照
class tracemalloc.
Snapshot
python分配的内存块的跟踪快照。
这个 take_snapshot()
函数创建快照实例。compare_to
(old_snapshot: Snapshot, key_type: str, cumulative: bool = False)
使用旧快照计算差异。将统计信息作为 StatisticDiff
实例分组依据 key_type .
见 Snapshot.statistics()
方法 key_type 和 累积的 参数。
结果按以下顺序从最大到最小排序:绝对值 StatisticDiff.size_diff
, StatisticDiff.size
,绝对值 StatisticDiff.count_diff
, Statistic.count
然后由 StatisticDiff.traceback
.dump
(filename)
将快照写入文件。
使用 load()
重新加载快照。filter_traces
(filters)
创建新的 Snapshot
已筛选的实例 traces
序列, 过滤器 是一个列表 DomainFilter
和 Filter
实例。如果 过滤器 是空列表,返回新的 Snapshot
带有跟踪副本的实例。
同时应用所有包含筛选器,如果没有匹配的包含筛选器,则忽略跟踪。如果至少有一个独占筛选器与跟踪匹配,则忽略该跟踪。
在 3.6 版更改: DomainFilter
实例现在也被接受 过滤器 .classmethod load
(filename)
从文件加载快照。
也见 dump()
.statistics
(key_type: str, cumulative: bool = False)
将统计信息作为 Statistic
实例分组依据 key_type :
key_type | 描述 |
---|---|
'filename' | 文件名 |
'lineno' | 文件名和行号 |
'traceback' | 追溯 |
如果 累积的 是 True
,累积跟踪的所有帧的内存块大小和计数,而不仅仅是最新帧。累积模式只能用于 key_type 等于 'filename'
和 'lineno'
.
结果按以下顺序从大到小排序: Statistic.size
, Statistic.count
然后由 Statistic.traceback
.traceback_limit
在跟踪中存储的最大帧数 traces
结果: get_traceback_limit()
拍摄快照时。traces
python分配的所有内存块的跟踪:序列 Trace
实例。
序列的顺序未定义。使用 Snapshot.statistics()
方法获取统计信息的排序列表。
统计的
class tracemalloc.
Statistic
内存分配统计。
Snapshot.statistics()
返回的列表 Statistic
实例。
也见 StatisticDiff
类。count
内存块数 (int
)size
内存块的总大小(字节) (int
)traceback
回溯内存块的分配位置, Traceback
实例。
StatisticDiff
class tracemalloc.
StatisticDiff
新旧内存分配的统计差异 Snapshot
实例。
Snapshot.compare_to()
返回的列表 StatisticDiff
实例。也见 Statistic
类。count
新快照中的内存块数 (int
): 0
如果内存块已在新快照中释放。count_diff
新快照和旧快照的内存块数之差 (int
): 0
如果已在新快照中分配内存块。size
新快照中内存块的总大小(字节) (int
): 0
如果内存块已在新快照中释放。size_diff
旧快照和新快照之间内存块的总大小(以字节为单位)的差异 (int
): 0
如果已在新快照中分配内存块。traceback
回溯内存块的分配位置, Traceback
实例。
跟踪
class tracemalloc.
Trace
内存块的跟踪。
这个 Snapshot.traces
属性是 Trace
实例。
在 3.6 版更改: 增加了 domain
属性。domain
内存块的地址空间 (int
)只读属性。
tracemalloc使用域 0
跟踪python进行的内存分配。C扩展可以使用其他域来跟踪其他资源。size
内存块的大小(字节) (int
)traceback
回溯内存块的分配位置, 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())
本文为原创文章,未经授权禁止转载本站文章。
原文出处:兰玉磊的个人博客
原文链接:https://www.fdevops.com/2021/03/17/tracemalloc-28085
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。