使用 tracemalloc 来跟踪分析 Python 程序的内存分配 | 您所在的位置:网站首页 › traces怎么读 › 使用 tracemalloc 来跟踪分析 Python 程序的内存分配 |
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 ] :716: size=4855 KiB, count=39328, average=126 B :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 :1446: size=70.4 KiB, count=911, average=79 B :1454: size=52.0 KiB, count=25, average=2131 B :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 ] :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 :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 "", line 716 File "", line 1036 File "", line 934 File "", line 1068 File "", line 619 File "", line 1581 File "", line 1614 File "/usr/lib/python3.4/doctest.py", line 101 import pdb File "", line 284 File "", line 938 File "", line 1068 File "", line 619 File "", line 1581 File "", 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行代码,分配具有漂亮输出的最大内存,忽略 和 文件夹:: import linecache import os import tracemalloc def display_top(snapshot, key_type='lineno', limit=10): snapshot = snapshot.filter_traces(( tracemalloc.Filter(False, ""), tracemalloc.Filter(False, ""), )) 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: :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() 功能。 DomainFilterclass 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, "") 排除空的回溯在 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 实例。 StatisticDiffclass 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 模块获取帧失败,文件名 "" 在行号处 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 国际」知识共享许可协议进行许可。 |
CopyRight 2018-2019 实验室设备网 版权所有 |