Как явно освободить память в Python?
Я написал программу на Python, которая обрабатывает большой входной файл и создает несколько миллионов объектов, представляющих треугольники. Алгоритм следующий:
- Чтение входного файла.
- Обработка файла и создание списка треугольников, представленных их вершинами.
- Вывод вершин в формате OFF: сначала список вершин, затем список треугольников. Треугольники представлены индексами в списке вершин.
Требование формата OFF, согласно которому я должен сначала вывести полный список вершин, а потом треугольники, означает, что мне нужно удерживать список треугольников в памяти до того, как я запишу данные в файл. В то же время я получаю ошибки памяти из-за размеров списков.
Какой самый эффективный способ сообщить Python, что некоторые данные мне больше не нужны и их можно освободить?
5 ответ(ов)
К сожалению, (в зависимости от вашей версии и релиза Python) некоторые типы объектов используют «свободные списки», что является удобной локальной оптимизацией, но может привести к фрагментации памяти. Это происходит из-за того, что все больше и больше памяти «забронировано» только для объектов определенного типа, и таким образом недоступно для «общего фонда».
Единственный действительно надежный способ гарантировать, что значительное, но временное использование памяти действительно вернется в систему после завершения, — это произвести это использование в подпроцессе, который выполнит ресурсоемкую работу и затем завершится. При таких условиях операционная система СДЕЛАЕТ свою работу и с радостью переработает все ресурсы, которые мог использовать подпроцесс. К счастью, модуль multiprocessing
делает такую операцию (которая раньше была довольно неудобной) гораздо проще в современных версиях Python.
В вашем случае кажется, что лучший способ для подпроцессов накапливать результаты и при этом гарантировать, что эти результаты будут доступны основному процессу, — это использовать полупроизвольные файлы (под полупроизвольными я имею в виду не такие файлы, которые автоматически исчезают при закрытии, а обычные файлы, которые вы явно удаляете, когда закончите с ними).
В Python используется сборка мусора, поэтому, если вы уменьшите размер вашего списка, память будет освобождена. Вы также можете использовать оператор "del", чтобы полностью удалить переменную:
biglist = [blah, blah, blah]
#...
del biglist
Это удалит переменную biglist
и освободит занятую ей память, если на этот объект больше нет ссылок. Однако стоит помнить, что сборка мусора может происходить не мгновенно, и освобождение памяти не гарантировано сразу после вызова del
.
del
может быть вашим другом, так как он помечает объекты как возможно удаляемые, когда на них больше нет ссылок. Однако часто интерпретатор CPython оставляет эту память для последующего использования, поэтому ваша операционная система может не видеть "освобожденную" память.
Возможно, вы вообще не столкнетесь с проблемами с памятью, если будете использовать более компактные структуры для ваших данных. Например, списки чисел значительно менее эффективны по использованию памяти, чем формат, используемый стандартным модулем array
или сторонним модулем numpy
. Вы сэкономите память, сохранив ваши вершины в массиве NumPy размером 3xN и ваши треугольники в массиве на N элементов.
Вы не можете явно освобождать память. Что вам нужно сделать, так это убедиться, что вы не храните ссылки на объекты. В таком случае они будут собраны сборщиком мусора, что освободит память.
В вашем случае, когда вам нужны большие списки, обычно требуется реорганизовать код, используя генераторы или итераторы. Таким образом, вам не нужно будет хранить большие списки в памяти вообще.
У меня была аналогичная проблема при чтении графа из файла. Обработка включала в себя вычисление матрицы размером 200 000x200 000 с плавающей запятой (по одной строке за раз), которая не вмещалась в память. Попытки освободить память между вычислениями с помощью gc.collect()
устранили проблемы с памятью, но привели к проблемам с производительностью: я не знаю, почему, но хотя объем используемой памяти оставался постоянным, каждый новый вызов gc.collect()
занимал больше времени, чем предыдущий. В результате сборка мусора занимала большую часть времени вычислений.
Чтобы решить проблемы с памятью и производительностью, я переключился на прием с использованием многопоточности, который я когда-то прочитал (извините, я не могу больше найти связанный пост). Ранее я читал каждую строку файла в большом цикле for
, обрабатывал ее и время от времени вызывал gc.collect()
, чтобы освободить память. Теперь я вызываю функцию, которая читает и обрабатывает часть файла в новом потоке. Как только поток завершает свою работу, память автоматически освобождается без странных проблем с производительностью.
Практически это работает так:
from dask import delayed # этот модуль оборачивает многопоточность
def f(storage, index, chunk_size): # функция обработки
# читаем часть размером chunk_size, начиная с index в файле
# обрабатываем ее, используя данные из storage, если это необходимо
# добавляем данные, необходимые для дальнейших вычислений, в storage
return storage
partial_result = delayed([]) # помещаем в delayed() конструктор вашей структуры данных
# Я лично использую "delayed(nx.Graph())", так как создаю граф из networkx
chunk_size = 100 # желательно, чтобы это значение было как можно больше, но при этом позволяло вычислениям помещаться в память
for index in range(0, len(file), chunk_size):
# мы указываем dask, что хотим применить f к параметрам partial_result, index, chunk_size
partial_result = delayed(f)(partial_result, index, chunk_size)
# пока вычисления не выполнены !
# dask создаст поток, чтобы выполнить f(partial_result, index, chunk_size), когда мы вызовем partial_result.compute()
# передавая предыдущую переменную "partial_result" в параметры, мы гарантируем, что часть будет обработана только после завершения предыдущей
# это также позволяет использовать результаты обработки предыдущих частей файла, если это необходимо
# это запускает все вычисления
result = partial_result.compute()
# для каждого "delayed" будет запущен один поток за раз для вычисления его результата
# затем dask завершает поток, что решает проблему освобождения памяти
# проблема с производительностью, вызванная gc.collect(), также избегается
Как проверить, использует ли PyTorch GPU?
Преобразование списка словарей в DataFrame pandas
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе
Как отменить последнюю миграцию?
Ошибка: "'dict' объект не имеет метода 'iteritems'"