tracemalloc — Отслеживание выделения памяти

Added in version 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(...))).

API

Функции

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().

Added in version 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().

DomainFilter

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

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

Added in version 3.6.

inclusive

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

Если inclusive равно 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, сгруппированных по ключу_типа.

См. метод Snapshot.statistics() для параметров ключ_типа и кумулятивный.

Результат сортируется от наибольшего к наименьшему по: абсолютной величине 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, сгруппированных по ключу_типа:

тип ключа

описание

'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(), чтобы получить отсортированный список статистики.

Статистика

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 экземпляр.

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())