Понимание роста памяти процесса Python (VmRSS vs g c .get_objects) - PullRequest
2 голосов
/ 11 июля 2020

Не вдаваясь в детали алгоритмов c, скажем, что мой код последовательно обрабатывает список входных данных:

inputs = [2,5,6,7,8,10,12,13,14,15,16,17,18,19,20,21]
for i in inputs:
    process_input(i)

Для простоты давайте рассмотрим process_input как черный box.

Я знаю, что на этом сайте заполнено вопросов о поиске утечек памяти в коде Python, но этот вопрос не об этом. Вместо этого я пытаюсь понять потребление памяти моим кодом с течением времени и может ли он страдать от утечки памяти.

В частности, I Я пытаюсь понять несоответствие двух различных индикаторов использования памяти:

Для изучения этих двух показателей, Я расширил исходный код сверху следующим образом:

import time, gc

def get_current_memory_usage():
    with open('/proc/self/status') as f:
        memusage = f.read().split('VmRSS:')[1].split('\n')[0][:-3]
    return int(memusage.strip()) / (1024 ** 2)

inputs = [2,5,6,7,8,10,12,13,14,15,16,17,18,19,20,21]
gc.collect()

last_object_count = len(gc.get_objects())

for i in inputs:

    print(f'\nProcessing input {i}...')
    process_input(i)

    gc.collect()
    time.sleep(1)
    memory_usage = get_current_memory_usage()
    object_count = len(gc.get_objects())
    print(f'Memory usage: {memory_usage:.2f} GiB')
    print(f'Object count: {object_count - last_object_count:+}')
    last_object_count = object_count

Обратите внимание, что process_input не имеет состояния, т.е. порядок входных данных не имеет значения. Таким образом, мы ожидаем, что оба индикатора будут примерно одинаковыми до запуска process_input и после , верно? Действительно, это то, что я наблюдаю по количеству выделенных объектов. Однако потребление памяти неуклонно растет:

индикаторы с течением времени

Теперь мой основной вопрос: Указывают ли эти наблюдения на утечку памяти? Насколько я понимаю, утечка памяти в Python будет обозначаться ростом выделенных объектов, которые мы не здесь наблюдаем. С другой стороны, почему потребление памяти неуклонно растет?

Для дальнейшего исследования я также провел второй тест. Для этого теста я неоднократно вызывал process_input(i), используя fixed input i (по пять раз каждый), и записывал потребление памяти между итерациями:

  • For i=12, потребление памяти осталось постоянным на уровне 10,91 ГиБ.
  • Для i=14 потребление памяти осталось постоянным на уровне 7,00 ГиБ.

Я думаю, эти наблюдения делают еще более маловероятным наличие утечки памяти, не так ли? Но тогда, , что могло бы быть возможным объяснением того, почему потребление памяти не падает между итерациями , учитывая, что process_input не имеет состояния?

Всего в системе 32 ГиБ ОЗУ, и она работает под управлением Ubuntu 20.04. Python версия - 3.6.10. Функция process_input использует несколько сторонних библиотек.

1 Ответ

1 голос
/ 14 июля 2020

В общем, RSS не является особенно хорошим индикатором, потому что это «резидентный» размер набора, и даже довольно скромный sh процесс с точки зрения выделенной памяти может иметь скромный RSS, поскольку память может быть выгружена. Вы можете посмотреть / proc / self / smaps и сложить размер доступных для записи регионов, чтобы получить гораздо лучший тест.

С другой стороны, если рост действительно есть, и вы хотите понять почему, вам нужно посмотреть на фактическую динамически выделяемую память. Для этого я бы посоветовал использовать https://github.com/vmware/chap

Для этого просто сделайте 1 секунду сна немного дольше, поместите печать непосредственно перед вызовом в сон и используйте gcore из другого сеанса, чтобы собрать живое ядро ​​во время нескольких из этих периодов сна. например, с помощью следующих команд:

count used

Это даст вам хорошее представление о выделениях, которые были запрошены, но не освобождены. Если цифры намного больше для более позднего ядра, у вас, вероятно, есть какие-то проблемы с ростом. Если эти числа действительно сильно различаются, используйте

summarize used

. Если у вас есть рост, возможно, есть утечка (в отличие от некоторого контейнера, просто расширяющегося). Чтобы проверить это, вы можете попробовать такие команды, как

count leaked
show leaked 

. Оттуда вам, вероятно, следует посмотреть документацию, в зависимости от того, что вы найдете.

OTOH, если используемые распределения не являются проблемой, возможно попробуйте следующее, чтобы увидеть память для выделений, которые были освобождены, но являются частью более крупных областей памяти, которые не могут быть возвращены операционной системе, поскольку части этих областей все еще используются:

count free
summarize free

Если ни «использованные», ни «свободные» выделения не являются проблемой, вы можете попробовать:

summarize writable

Это очень высокоуровневое представление всей доступной для записи памяти. Например, вы можете увидеть такие вещи, как использование стека ...

...