Как я могу явно освободить память в Python? - PullRequest
301 голосов
/ 22 августа 2009

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

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

Требование ВЫКЛЮЧЕНА, чтобы я распечатывал полный список вершин перед тем, как распечатать треугольники, означает, что я должен держать список треугольников в памяти, прежде чем записать вывод в файл. Тем временем я получаю ошибки памяти из-за размеров списков.

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

Ответы [ 9 ]

331 голосов
/ 22 августа 2009

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

import gc
gc.collect()
102 голосов
/ 22 августа 2009

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

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

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

39 голосов
/ 22 августа 2009

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

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

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

27 голосов
/ 22 августа 2009

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

biglist = [blah,blah,blah]
#...
del biglist
18 голосов
/ 22 августа 2009

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

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

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

16 голосов
/ 23 августа 2009

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

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

9 голосов
/ 22 августа 2009

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

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

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

7 голосов
/ 11 сентября 2017

У меня была похожая проблема при чтении графика из файла. Обработка включала вычисление матрицы с плавающей запятой 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
3 голосов
/ 22 августа 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...