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 КіБ вихідного коду 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 (include), збігаються блоки пам’яті, виділені в адресному просторі domain.

Якщо inclusive має значення False (виключити), збігаються блоки пам’яті, не виділені в адресному просторі domain.

domain

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

фільтр

class tracemalloc.Filter(inclusive: bool, filename_pattern: str, lineno: int = None, all_frames: bool = False, domain: int = None)

Фільтрувати сліди блоків пам’яті.

Перегляньте функцію 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 (include), збігаються лише блоки пам’яті, виділені у файлі з іменем, яке відповідає 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 тепер також приймаються в filters.

classmethod load(filename)

Завантажте знімок із файлу.

Дивіться також dump().

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

Отримати статистику як відсортований список екземплярів Statistic, згрупованих за key_type:

key_type

опис

'ім'я файлу'

ім’я файлу

''лінено'

ім’я файлу та номер рядка

'відстеження''

простежити

Якщо cumulative має значення True, кумулювати розмір і кількість блоків пам’яті всіх кадрів зворотного відстеження трасування, а не лише останнього кадру. Кумулятивний режим можна використовувати лише з key_type, що дорівнює 'filename' і 'lineno'.

Результат сортується від найбільшого до найменшого за: Statistic.size, Statistic.count, а потім за Statistic.traceback.

traceback_limit

Максимальна кількість кадрів, що зберігаються у зворотній трасуванні traces: результату get_traceback_limit() під час створення знімка.

traces

Трасування всіх блоків пам’яті, виділених Python: послідовність екземплярів Trace.

Послідовність має невизначений порядок. Використовуйте метод Snapshot.statistic(), щоб отримати відсортований список статистики.

статистика

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, щоб отримати рядки з вихідного коду. Якщо встановлено ліміт, відформатуйте ліміт останніх кадрів, якщо ліміт додатний. В іншому випадку відформатуйте найстаріші кадри 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())