Как я могу явно освободить память в Python?


389

Я написал программу на Python, которая работает с большим входным файлом, создавая несколько миллионов объектов, представляющих треугольники. Алгоритм:

  1. читать входной файл
  2. обработать файл и создать список треугольников, представленных их вершинами
  3. Выведите вершины в формате OFF: список вершин, за которым следует список треугольников. Треугольники представлены индексами в списке вершин

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

Как лучше всего сказать Python, что мне больше не нужны некоторые данные, и их можно освободить?


11
Почему бы не распечатать треугольники в промежуточный файл и не прочитать их снова, когда они понадобятся?
Алиса Перселл

2
Этот вопрос потенциально может быть о двух совершенно разных вещах. Это ошибки одного и того же процесса Python , и в этом случае мы заботимся о том, чтобы освободить память для кучи процесса Python, или они связаны с различными процессами в системе, и в этом случае мы заботимся об освобождении памяти для ОС?
Чарльз Даффи

Ответы:


456

В соответствии с официальной документацией Python вы можете заставить сборщик мусора освобождать память, на которую нет ссылок gc.collect(). Пример:

import gc
gc.collect()

19
В любом случае, вещи часто собираются мусором, за исключением некоторых необычных случаев, поэтому я не думаю, что это сильно поможет.
Леннарт Регебро

24
В общем, gc.collect () следует избегать. Сборщик мусора знает, как делать свою работу. Тем не менее, если ОП находится в ситуации, когда он внезапно освобождает множество объектов (например, от миллионов), gc.collect может оказаться полезным.
Джейсон Бейкер

165
Фактически вызов gc.collect()себя в конце цикла может помочь избежать фрагментации памяти, что, в свою очередь, помогает поддерживать производительность. Я видел, как это существенно
меняет

39
Я использую Python 3.6. Вызов gc.collect()после загрузки кадра данных pandas из hdf5 (500 тыс. Строк) уменьшил использование памяти с 1,7 ГБ до 500 МБ
Джон

15
Мне нужно загрузить и обработать несколько массивов по 25 ГБ в системе с 32 ГБ памяти. Использование с del my_arrayпоследующим gc.collect()после обработки массивом является единственным способом, которым память фактически освобождается, и мой процесс выживает, чтобы загрузить следующий массив.
Дэвид

113

К сожалению (в зависимости от вашей версии и выпуска Python) некоторые типы объектов используют «свободные списки», которые представляют собой аккуратную локальную оптимизацию, но могут вызвать фрагментацию памяти, в частности, делая все больше и больше памяти «выделенными» только для объектов определенного типа и тем самым недоступен «общему фонду».

Единственный действительно надежный способ гарантировать, что большое, но временное использование памяти ДОЛЖНО возвращать все ресурсы системе, когда это будет сделано, состоит в том, чтобы такое использование происходило в подпроцессе, который выполняет работу, требующую памяти, а затем завершается. В таких условиях операционная система выполнит свою работу и с удовольствием утилизирует все ресурсы, которые подпроцесс мог поглотить. К счастью, multiprocessingмодуль делает такую ​​операцию (которая раньше была довольно болезненной) не слишком плохой в современных версиях Python.

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


31
Я уверен, что хотел бы увидеть тривиальный пример этого.
Аарон Холл

3
Шутки в сторону. Что сказал @AaronHall.
Нуб Сайбот

17
@AaronHall Тривиальный пример теперь доступен , используя multiprocessing.Managerвместо файлов для реализации общего состояния.
user4815162342

48

Это delзаявление может быть полезным, но IIRC не гарантирует освобождение памяти . Эти документы здесь ... и почему он не избавлен здесь .

Я слышал, как люди в системах Linux и Unix разрабатывают процесс Python, чтобы выполнить некоторую работу, получить результаты и затем убить их.

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


Будут ли IronPython и Jython еще одним способом избежать этой проблемы?
Эстебан Кюбер

@voyager: Нет, не будет. И ни один другой язык, на самом деле. Проблема в том, что он читает большие объемы данных в список, а данные слишком велики для памяти.
Леннарт Регебро

1
Скорее всего, будет хуже под IronPython или Jython. В этих средах вы даже не гарантируете, что память будет освобождена, если ничто другое не содержит ссылку.
Джейсон Бейкер

@voyager, да, потому что виртуальная машина Java ищет глобально для освобождения памяти. Для JVM в Jython нет ничего особенного. С другой стороны, у JVM есть своя собственная доля недостатков, например, вы должны заранее объявить, какую большую кучу она может использовать.
Профессор Фалькен нарушил контракт

32

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

biglist = [blah,blah,blah]
#...
del biglist

18
Это и не правда. Хотя уменьшение размера списка позволяет восстановить память, нет гарантии, когда это произойдет.
user142350

3
Нет, но обычно это поможет. Однако, как я понимаю вопрос, проблема в том, что у него должно быть так много объектов, что ему не хватает памяти перед обработкой их всех, если он читает их в список. Удаление списка до его обработки вряд ли будет полезным решением. ;)
Леннарт Регебро

3
Не приведет ли условие нехватки памяти / нехватка памяти к «аварийному запуску» сборщика мусора?
Джереми Фризнер

4
biglist = [] освободит память?
neouyghur

3
да, если на старый список больше нет ссылок
Нед Бэтчелдер

22

Вы не можете явно освободить память. Вам нужно убедиться, что вы не храните ссылки на объекты. Затем они будут собирать мусор, освобождая память.

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

http://www.prasannatech.net/2009/07/introduction-python-generators.html


1
Если такой подход возможен, то, вероятно, стоит сделать. Но следует отметить, что вы не можете использовать произвольный доступ к итераторам, что может вызвать проблемы.
Джейсон Бейкер

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

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

Верно, но тогда вам придется перебирать все, чтобы получить подмножество, которое будет очень медленным.
Леннарт Регебро

21

( delможет быть вашим другом, так как он помечает объекты как удаляемые, когда на них нет других ссылок. Теперь часто интерпретатор CPython сохраняет эту память для дальнейшего использования, поэтому ваша операционная система может не видеть «освобожденную» память.)

Возможно, вы не столкнетесь с какими-либо проблемами с памятью, если будете использовать более компактную структуру для ваших данных. Таким образом, списки чисел намного менее эффективны по памяти, чем формат, используемый стандартным arrayмодулем или сторонним numpyмодулем. Вы бы сэкономили память, поместив свои вершины в массив NumPy 3xN, а треугольники в массив N-элементов.


А? Сборка мусора в CPython основана на пересчетах; это не периодическая метка-развертка (как для многих распространенных реализаций JVM), но вместо этого она немедленно удаляет что-то, как только счетчик ссылок достигает нуля. Только циклы (где refcounts будет нулевым, но не из-за циклов в дереве ссылок) требуют периодического обслуживания. delне делает ничего, что просто переназначает другое значение всем именам, ссылающимся на объект, не будет.
Чарльз Даффи

Я вижу, откуда вы: я обновлю ответ соответственно. Я понимаю, что интерпретатор CPython на самом деле работает некоторым промежуточным способом: delосвобождает память с точки зрения Python, но, как правило, не с точки зрения библиотеки времени выполнения C или ОС. Ссылки: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/… .
Эрик О Лебигот

Согласившись с содержанием ваших ссылок, но если предположить, что OP говорит об ошибке, которую они получают от одного и того же процесса Python , различие между освобождением памяти для локальной кучи процесса и для ОС, похоже, не имеет значения ( поскольку освобождение до кучи делает это пространство доступным для новых выделений в этом процессе Python). И для этого, delодинаково эффективен с выходами из области, переназначениями и т. Д.
Чарльз Даффи

11

У меня была похожая проблема при чтении графика из файла. Обработка включала вычисление матрицы с плавающей запятой 200 000x200 000 (по одной строке за раз), которая не помещалась в память. Попытка освободить память между вычислениями с помощью gc.collect()исправила связанный с памятью аспект проблемы, но это привело к проблемам с производительностью: я не знаю, почему, хотя объем используемой памяти оставался постоянным, каждый новый вызов gc.collect()занимал немного больше времени, чем предыдущий. Так что довольно быстро сборка мусора заняла большую часть времени вычислений.

Чтобы исправить проблемы с памятью и производительностью, я переключился на использование многопоточного трюка, который я однажды где-то читал (извините, я больше не могу найти соответствующий пост). До того, как я прочитал каждую строку файла в большом forцикле, обработал его и запускал gc.collect()время от времени, чтобы освободить место в памяти. Теперь я вызываю функцию, которая читает и обрабатывает кусок файла в новом потоке. Как только поток заканчивается, память автоматически освобождается без странной проблемы с производительностью.

Практически это работает так:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
Интересно, почему вы используете `//` `s вместо # в Python для комментариев.
JC Rocamonde

Я перепутал языки. Спасибо за замечание, я обновил синтаксис.
Рецод

9

Другие опубликовали некоторые способы, которыми вы могли бы «уговорить» интерпретатор Python освободить память (или иначе избежать проблем с памятью). Скорее всего, вы должны попробовать их идеи в первую очередь. Тем не менее, я считаю важным дать вам прямой ответ на ваш вопрос.

На самом деле нет никакого способа напрямую сказать Python освободить память. Дело в том, что если вам нужен такой низкий уровень контроля, вам придется написать расширение на C или C ++.

Тем не менее, есть несколько инструментов, которые помогут с этим:


3
gc.collect () и del gc.garbage [:] отлично работают, когда я использую большие объемы памяти
Эндрю Скотт Эванс,

3

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


1
Я полагаю, что могу сохранить только вершины в памяти и распечатать треугольники в файл, а затем распечатать вершины только в конце. Однако процесс записи треугольников в файл приводит к огромному снижению производительности. Есть ли способ ускорить это ?
Натан Феллман
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.