Обнаружение утечки памяти в python модулем tracemallo c - PullRequest
0 голосов
/ 18 апреля 2020

У меня есть сценарий python, который использует модель Pytorch с открытым исходным кодом, и этот код имеет утечку памяти. Я запускаю это с memory_profiler mprof run --include-children python my_sctipt.py и получаю следующее изображение: menory_profiler plot

Я пытаюсь найти причину утечки системой python модуль tracemallo c:

tracemalloc.start(25)
while True:
    ...
    snap = tracemalloc.take_snapshot()
    domain_filter = tracemalloc.DomainFilter(True, 0)
    snap = snap.filter_traces([domain_filter])
    stats = snap.statistics('lineno', True)
    for stat in stats[:10]:
        print(stat)

Если посмотреть только на вывод tracemallo c, я не смогу определить проблему. Я предполагаю, что проблема в расширении C, но я хотел бы убедиться, что это правда. Я пытался изменить домен с помощью DomainFilter, но у меня есть вывод только в домене 0.

Кроме того, я не понимаю значения параметра, который получил tracemalloc.start(frameno), frameno - это число наиболее последние кадры, но ничего не происходит, когда я меняю его.

Что я могу сделать дальше, чтобы найти проблемное c место в коде, которое вызывает утечку памяти?

С нетерпением ждем вашего ответа .

1 Ответ

0 голосов
/ 18 апреля 2020

Учитывая, что вы предполагаете, что проблема в расширении C, но вы хотите убедиться, что это правда, я бы посоветовал вам сделать это, используя инструмент с меньшим значением python -specifi c как https://github.com/vmware/chap или, по крайней мере, если вы можете запустить вашу программу на Linux.

Что вам нужно будет сделать, это запустить ваш скрипт (без инструкций) и в какой-то момент собрать живое ядро ​​(например, используя «gcore pid-of-your-running-program»).

Как только у вас будет это ядро, откройте это ядро ​​в chap («chap your-core-file-path» ") и попробуйте следующую команду из приглашения chap:

суммировать доступные для записи

Вывод будет примерно таким, но ваши цифры, вероятно, будут значительно отличаться:

chap> summarize writable
5 ranges take 0x2021000 bytes for use: stack
6 ranges take 0x180000 bytes for use: python arena
1 ranges take 0xe1000 bytes for use: libc malloc main arena pages
4 ranges take 0x84000 bytes for use: libc malloc heap
8 ranges take 0x80000 bytes for use: used by module
1 ranges take 0x31000 bytes for use: libc malloc mmapped allocation
4 ranges take 0x30000 bytes for use: unknown
29 writable ranges use 0x23e7000 (37,646,336) bytes.

Строки в сводке приведены в порядке убывания использования байтов, поэтому вы можете следовать этому порядку. Итак, посмотрев сначала на верхний, мы видим, что используется «стек»:

5 ranges take 0x2021000 bytes for use: stack

Это конкретное ядро ​​было для очень простой python программы, которая запускает 4 дополнительных потока и спит все 5 потоков. Причиной большого выделения стека может быть довольно легко с многопоточной программой python, которая заключается в том, что python использует pthreads для создания дополнительных потоков, а pthreads использует значение ulimit для размера стека по умолчанию. Если ваша программа имеет такое же большое значение, вы можете изменить размер стека одним из нескольких способов, включая запуск «ulimit -s» в родительском процессе, чтобы изменить размер стека по умолчанию. Чтобы увидеть, какие значения действительно имеют смысл, вы можете использовать следующую команду из приглашения главы:

chap> describe stacks
Thread 1 uses stack block [0x7fffe22bc000, 7fffe22dd000)
 current sp: 0x7fffe22daa00
Peak stack usage was 0x7798 bytes out of 0x21000 total.

Thread 2 uses stack block [0x7f51ec07c000, 7f51ec87c000)
 current sp: 0x7f51ec87a750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

Thread 3 uses stack block [0x7f51e7800000, 7f51e8000000)
 current sp: 0x7f51e7ffe750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

Thread 4 uses stack block [0x7f51e6fff000, 7f51e77ff000)
 current sp: 0x7f51e77fd750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

Thread 5 uses stack block [0x7f51e67fe000, 7f51e6ffe000)
 current sp: 0x7f51e6ffc750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

5 stacks use 0x2021000 (33,689,600) bytes.

Итак, вы видите выше, что 4 стека имеют размер 8 МБ, но могут легко подойти под 64 КБ.

Ваша программа может не иметь проблем с размером стека, но если это так, вы можете исправить их, как описано выше.

Продолжая проверку причин роста, посмотрите следующую строку из итоги:

6 ranges take 0x180000 bytes for use: python arena

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

Глядя на оставшиеся строки сводки, мы видим несколько с "lib c" как часть описания "use":

1 ranges take 0xe1000 bytes for use: libc malloc main arena pages
4 ranges take 0x84000 bytes for use: libc malloc heap
1 ranges take 0x31000 bytes for use: libc malloc mmapped allocation

Обратите внимание, что lib c отвечает за всю эту память, но вы не можете знать, что память используется для не python кода, потому что для выделений, превышающих определенный порог размера (значительно ниже 4 КБ), python захватывает память через mallo c, а не захват памяти из одной из python арен.

Итак, давайте предположим, что вы решили все проблемы, которые могли возникнуть с использованием стека, и у вас в основном "python arenas" или "lib c mallo c "связанные использования. Следующее, что вы хотите понять, является ли эта память в основном «используемой» (имеется в виду выделенная, но никогда не освобождаемая) или «свободной» (имеется в виду «освобожденная, но не возвращенная операционной системе). Вы можете сделать это, как показано:

chap> count used
15731 allocations use 0x239388 (2,331,528) bytes.
chap> count free
1563 allocations use 0xb84c8 (754,888) bytes.

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

Итак, давайте пока предположим, что используемые распределения являются основной причиной роста в вашем случае. Мы можем выяснить, почему у нас так много используемых распределений.

Первое, что мы могли бы узнать, были ли какие-либо ассигнования фактически «просочились» в том смысле, что они более недоступны. Это исключает случай, когда рост происходит из-за роста на основе контейнеров.

One делает это следующим образом:

chap> summarize leaked
0 allocations use 0x0 (0) bytes.

Так что для этого конкретного ядра, как это обычно бывает для python ядер, noth Инг утечка. Ваш номер может быть ненулевым. Если он не равен нулю, но все же намного ниже итоговых значений, связанных с памятью, использованной для "python" или "lib c", о которых сообщалось выше, вы можете просто заметить утечки, но продолжить искать истинную причину роста. В руководстве пользователя есть некоторая информация о расследовании утечек, но она немного скудна. Если количество утечек на самом деле достаточно велико, чтобы объяснить вашу проблему роста, вам следует изучить это в следующем, но если нет, то читайте дальше.

Теперь, когда вы предполагаете рост на основе контейнеров, следующие команды полезны:

chap> redirect on
chap> summarize used
Wrote results to scratch/core.python_5_threads.summarize_used
chap> summarize used /sortby bytes
Wrote results to scratch/core.python_5_threads.summarize_used::sortby:bytes

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

В настоящее время chap имеет очень ограниченную поддержку python (он находит эти python объекты, в дополнение к любым выделенным lib c mallo c, но для python объектов сводка выделяет только ограниченные категории для python объекты в терминах шаблонов (например,% SimplePythonObject соответствует таким вещам, как «int», «str», ..., которые не содержат других python объектов, а% ContainerPythonObject соответствует таким вещам, как tuple, list, dict, ... которые содержат ссылки на другие python объекты. С учетом сказанного, из сводки должно быть довольно легко определить, растет ли th в используемых распределениях в основном из-за объектов, выделенных python или объектов, выделенных нативным кодом.

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

Pattern %SimplePythonObject has 7798 instances taking 0x9e9e8(649,704) bytes.

Pattern %ContainerPythonObject has 7244 instances taking 0xc51a8(807,336) bytes.

Pattern %PyDictKeysObject has 213 instances taking 0xb6730(747,312) bytes.

Так что в ядре, которое я использовал в качестве примера, определенно python распределения преобладают.

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

Unrecognized allocations have 474 instances taking 0x1e9b8(125,368) bytes.

Это, мы надеемся, ответит на ваш базовый c вопрос о том, что вы можете делать дальше. По крайней мере, в этот момент вы поймете, вероятен ли рост из-за кода C или python, и в зависимости от того, что вы найдете, руководство пользователя главы должно помочь вам go дальше.

...