Утечки памяти Python
У меня есть долго работающий скрипт, который, если его запустить достаточно долго, будет занимать всю память в моей системе.
Не вдаваясь в подробности о сценарии, у меня есть два вопроса:
- Существуют ли какие-либо "передовые практики", которым нужно следовать, которые помогут предотвратить возникновение утечек?
- Какие методы существуют для устранения утечек памяти в Python?
9 ответов
Взгляните на эту статью: Отслеживание утечек памяти на python
Также обратите внимание, что модуль сбора мусора на самом деле может иметь установленные флаги отладки. Посмотрите на set_debug
функция. Кроме того, посмотрите на этот код Gnibbler для определения типов объектов, которые были созданы после вызова.
Я опробовал большинство опций, упомянутых ранее, но нашел этот небольшой и интуитивно понятный пакет лучшим: pympler
Очень просто отследить объекты, которые не были собраны в мусор, посмотрите этот небольшой пример:
установить пакет через pip install pympler
from pympler.tracker import SummaryTracker
tracker = SummaryTracker()
# ... some code you want to investigate ...
tracker.print_diff()
Выходные данные показывают все объекты, которые были добавлены, а также память, которую они использовали.
Образец вывода:
types | # objects | total size
====================================== | =========== | ============
list | 1095 | 160.78 KB
str | 1093 | 66.33 KB
int | 120 | 2.81 KB
dict | 3 | 840 B
frame (codename: create_summary) | 1 | 560 B
frame (codename: print_diff) | 1 | 480 B
Этот пакет предоставляет ряд дополнительных функций. Проверьте документацию pympler, в частности, раздел " Определение утечек памяти".
Позвольте мне порекомендовать инструмент mem_top,
это помогло мне решить аналогичную проблему.
Он просто мгновенно показывает главных подозреваемых на утечки памяти в программе Python.
Модуль Tracemalloc был интегрирован как встроенный модуль, начиная с Python 3.4, и, по-видимому, он также доступен для предыдущих версий Python в качестве сторонней библиотеки (хотя и не тестировал его).
Этот модуль способен выводить точные файлы и строки, которые занимали больше всего памяти. ИМХО, эта информация бесконечно более ценна, чем количество выделенных экземпляров для каждого типа (что в итоге приводит к множеству кортежей в 99% случаев, что является подсказкой, но в большинстве случаев едва помогает).
Я рекомендую вам использовать tracemalloc в сочетании с пиразитом. В 9 случаях из 10 запуск 10 лучших фрагментов в оболочке из пиразита даст вам достаточно информации и подсказок, чтобы исправить утечку в течение 10 минут. Тем не менее, если вы все еще не можете найти причину утечки, pyrasite-shell в сочетании с другими инструментами, упомянутыми в этой теме, вероятно, даст вам еще несколько советов. Вы также должны взглянуть на все дополнительные помощники, предоставляемые пиразитом (например, просмотрщик памяти).
Вы должны специально взглянуть на свои глобальные или статические данные (долгоживущие данные).
Когда эти данные растут без ограничений, вы также можете получить проблемы в Python.
Сборщик мусора может только собирать данные, на которые больше нет ссылок. Но ваши статические данные могут подключать элементы данных, которые должны быть освобождены.
Другой проблемой могут быть циклы памяти, но, по крайней мере, теоретически сборщик мусора должен находить и устранять циклы - по крайней мере, до тех пор, пока они не подключены к некоторым долгоживущим данным.
Какие виды долгоживущих данных особенно неприятны? Внимательно просматривайте любые списки и словари - они могут расти без ограничений. В словарях вы, возможно, даже не увидите появления проблемы, поскольку при доступе к диктовкам количество ключей в словаре может быть не слишком заметным для вас...
Для обнаружения и обнаружения утечек памяти для длительных процессов, например, в производственных средах, теперь вы можете использовать стэкинг-эффект. Он использует tracemalloc внизу. Больше информации в этом посте.
Что касается лучших практик, следите за рекурсивными функциями. В моем случае я столкнулся с проблемами с рекурсией (там, где не было необходимости). Упрощенный пример того, что я делал:
def my_function():
# lots of memory intensive operations
# like operating on images or huge dictionaries and lists
.....
my_flag = True
if my_flag: # restart the function if a certain flag is true
my_function()
def main():
my_function()
работа в таком рекурсивном режиме не вызовет сборку мусора и не очистит остатки функции, поэтому каждый раз при использовании памяти растет и растет.
Мое решение состояло в том, чтобы извлечь рекурсивный вызов из my_function() и иметь дескриптор main(), когда вызывать его снова. таким образом, функция завершается естественным образом и очищается после себя.
def my_function():
# lots of memory intensive operations
# like operating on images or huge dictionaries and lists
.....
my_flag = True
.....
return my_flag
def main():
result = my_function()
if result:
my_function()
Не уверен насчет "лучших практик" для утечек памяти в python, но python должен очистить свою собственную память сборщиком мусора. Так что в основном я бы начал с проверки кругового списка некоторых коротких, поскольку они не будут собраны сборщиком мусора.
Это ни в коем случае не исчерпывающий совет. Но первое, о чем следует помнить, когда пишете с мыслью избежать будущих утечек памяти (циклов), это убедиться, что все, что принимает ссылку на обратный вызов, должно хранить этот обратный вызов как слабую ссылку.
Я нашел очень полезную статью о том, как Garbage Collector работает в Python, который отвечает на оба ваших вопроса: https://rushter.com/blog/python-garbage-collector/