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

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

Добавлено в версии 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, посмотрев на атрибут 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)

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

Добавлено в версии 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 экземпляров, сгруппированных по key_type.

См. метод 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 экземпляров, сгруппированных по key_type:

тип_ключа

описание

'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, где были выделены блоки памяти, 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())
Вернуться на верх