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 загружались данные в последний раз: в строке 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(...))).

интерфейс прикладного программирования

Функции

tracemalloc.clear_traces()

Очистите следы блоков памяти, выделенных Python.

Смотрите также stop().

tracemalloc.get_object_traceback(obj)

Получите обратную трассировку, в которой был выделен объект Python obj. Верните экземпляр 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. Собранные результаты отслеживания будут ограничены кадрами nframe. По умолчанию трассировка блока памяти сохраняет только самый последний кадр: предел равен 1. nframe должно быть больше или равно 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(). Используйте параметр nframe функции start() для сохранения большего количества кадров.

Модуль tracemalloc должен отслеживать распределение памяти, чтобы сделать снимок, смотрите функцию start().

Смотрите также функцию get_object_traceback().

Доменный фильтр

class tracemalloc.DomainFilter(inclusive: bool, domain: int)

Фильтруйте следы блоков памяти по их адресному пространству (домену).

Добавлено в версии 3.6.

inclusive

Если inclusive равно True (включить), сопоставьте блоки памяти, выделенные в адресном пространстве domain.

Если включено равно False (исключить), сопоставьте блоки памяти, не выделенные в адресном пространстве domain.

domain

Адресное пространство блока памяти (int). Свойство, доступное только для чтения.

Фильтр

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.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 и cumulative.

Результат сортируется от наибольшего к наименьшему по: абсолютному значению StatisticDiff.size_diff, StatisticDiff.size, абсолютному значению StatisticDiff.count_diff, Statistic.count и затем по StatisticDiff.traceback.

dump(filename)

Запишите снимок в файл.

Используйте load() для перезагрузки моментального снимка.

filter_traces(filters)

Создайте новый экземпляр Snapshot с отфильтрованной последовательностью traces, filters - это список экземпляров DomainFilter и Filter. Если filters - пустой список, верните новый экземпляр Snapshot с копией трассировок.

Все включающие фильтры применяются одновременно, трассировка игнорируется, если ни один из включающих фильтров не соответствует ей. Трассировка игнорируется, если ей соответствует хотя бы один исключающий фильтр.

Изменено в версии 3.6: DomainFilter экземпляры теперь также принимаются в фильтрах.

classmethod load(filename)

Загрузите моментальный снимок из файла.

Смотрите также dump().

statistics(key_type: str, cumulative: bool = False)

Получите статистику в виде отсортированного списка экземпляров Statistic, сгруппированных по key_type:

тип ключа

описание

'filename'

имя файла

'lineno'

имя файла и номер строки

'traceback'

обратная связь

Если значение cumulative равно 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.

Статистический разброс

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())
Вернуться на верх