Освобождение памяти в Python


у меня есть несколько вопросов относительно использования памяти в следующем примере.

  1. если я запускаю в интерпретаторе,

    foo = ['bar' for _ in xrange(10000000)]
    

    реальная память, используемая на моей машине, доходит до 80.9mb. Я тогда,

    del foo
    

    реальная память идет вниз, но только до 30.4mb. Интерпретатор использует 4.4mb базовая линия так в чем же преимущество в том, чтобы не выпускать 26mb памяти для ОС? Это потому, что Python "планирует вперед", думая, что вы можете использовать опять столько воспоминаний?

  2. почему он выпускает 50.5mb в частности - какова сумма, которая выделяется на основе?

  3. есть ли способ заставить Python освободить всю память, которая использовалась (если вы знаете, что больше не будете использовать столько памяти)?

3 100

3 ответа:

память, выделенная в куче, может быть подвержена высоким отметкам воды. Это осложняется внутренней оптимизацией Python для выделения небольших объектов (PyObject_Malloc) в 4 пулах KiB, классифицированных для размеров распределения в кратных 8 байтам -- до 256 байт (512 байт в 3.3). Сами пулы находятся в 256 КИБ аренах, поэтому, если используется только один блок в одном пуле, вся 256 КИБ Арена не будет выпущена. В Python 3.3 распределитель небольших объектов был переключен на использование анонимных карт памяти вместо кучи, поэтому он должен работать лучше при освобождении памяти.

кроме того, встроенный в видах поддержания freelists ранее выделенных объектов, которые могут или не могут использовать небольшой объект выделения. Элемент int тип поддерживает freelist с собственной выделенной памятью, и для его очистки требуется вызов PyInt_ClearFreeList(). Это можно назвать косвенно, сделав полный gc.collect.

Попробуй вот это, и скажите мне, что вы получаете. Вот ссылка для psutil.

import os
import gc
import psutil

proc = psutil.Process(os.getpid())
gc.collect()
mem0 = proc.get_memory_info().rss

# create approx. 10**7 int objects and pointers
foo = ['abc' for x in range(10**7)]
mem1 = proc.get_memory_info().rss

# unreference, including x == 9999999
del foo, x
mem2 = proc.get_memory_info().rss

# collect() calls PyInt_ClearFreeList()
# or use ctypes: pythonapi.PyInt_ClearFreeList()
gc.collect()
mem3 = proc.get_memory_info().rss

pd = lambda x2, x1: 100.0 * (x2 - x1) / mem0
print "Allocation: %0.2f%%" % pd(mem1, mem0)
print "Unreference: %0.2f%%" % pd(mem2, mem1)
print "Collect: %0.2f%%" % pd(mem3, mem2)
print "Overall: %0.2f%%" % pd(mem3, mem0)

выход:

Allocation: 3034.36%
Unreference: -752.39%
Collect: -2279.74%
Overall: 2.23%

Edit:

я переключился на измерение относительно размера VM процесса, чтобы исключить влияние других процессов в системе.

среда выполнения C (например, glibc, msvcrt) сжимает кучу, когда смежное свободное пространство в верхней части достигает постоянного, динамического или настраиваемого порога. С glibc вы можете настроить это с mallopt (M_TRIM_THRESHOLD). Учитывая это, это не удивительно, если куча сжимается больше, даже намного больше, чем блок, что вы free.

в 3.x range не создает список, поэтому тест выше не будет создавать 10 миллионов int объекты. Даже если и так, то int введите 3.x-это в основном 2.x long, которые не реализовать freelist аппликации.

Я предполагаю, что вопрос, который вы действительно заботитесь о здесь:

есть ли способ заставить Python освободить всю память, которая использовалась (если вы знаете, что больше не будете использовать столько памяти)?

нет, нет. Но есть простой обходной путь: дочерние процессы.

Если вам нужно 500 МБ временного хранилища в течение 5 минут, но после этого вам нужно запустить еще 2 часа и больше не будет касаться этой памяти, порождайте ребенок процесс, чтобы сделать интенсивную работу памяти. Когда дочерний процесс уходит, память освобождается.

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

во-первых, самый простой способ создать дочерний процесс-это с concurrent.futures (или, для 3.1 и ранее,futures backport на PyPI):

with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
    result = executor.submit(func, *args, **kwargs).result()

Если вам нужно немного больше контроля, используйте multiprocessing модуль.

затраты:

  • процесс запуска является своего рода медленным на некоторых платформах, в частности Windows. Мы говорим здесь о миллисекундах, а не о минутах, и если вы заставляете одного ребенка выполнять работу за 300 секунд, вы даже не заметите этого. Но это не бесплатно.
  • если вы действительно используете большой объем временной памяти большой, это может привести к тому, что ваша основная программа будет заменена. Из конечно, вы экономите время в долгосрочной перспективе, потому что, если бы эта память висела вечно, она должна была бы привести к замене в какой-то момент. Но это может превратить постепенную медлительность в очень заметные все-сразу (и ранние) задержки в некоторых случаях использования.
  • передачу больших объемов данных между процессами может быть медленным. Опять же, если вы говорите об отправке более 2K аргументов и получении 64K результатов, вы даже не заметите этого, но если вы отправляете и получаете большие суммы данные, вы хотите использовать какой-то другой механизм (файл, mmapped или иначе; API общей памяти в multiprocessing; etc.).
  • отправка больших объемов данных между процессами означает, что данные должны быть рассольными (или, если вы вставляете их в файл или общую память,struct-в состоянии или в идеале ctypes-в состоянии).

eryksun ответил на вопрос №1, и я ответил на вопрос №3 (Оригинал №4), но теперь давайте ответим на вопрос № 2:

почему он выпускает 50,5 Мб в частности-какова сумма, которая выпускается на основе?

то, на чем он основан, в конечном счете, целая серия совпадений внутри Python и malloc Это очень трудно предсказать.

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

или вы можете измерять используемые страницы, которые могут или не могут рассчитывать выделенные, но никогда не трогаемые страницы (в системах, которые оптимистично выделяют, например linux), страницы, которые выделены, но помечены MADV_FREE и т. д.

если вы действительно измеряете выделенные страницы (что на самом деле не очень полезно чтобы сделать, но это, кажется, то, о чем вы спрашиваете), и страницы действительно были освобождены, два обстоятельства, при которых это может произойти: либо вы использовали brk или эквивалент сокращения сегмента данных (очень редко в настоящее время), или вы использовали munmap или аналогично освободить сопоставленный сегмент. (Есть также теоретически незначительный вариант для последнего, в том, что есть способы освободить часть отображенного сегмента-например, украсть его с MAP_FIXED на MADV_FREE сегмент, который вы сразу отменить.)

но большинство программ непосредственно не выделяют вещи из страниц памяти; они используют malloc-стиль выделения. Когда вы звоните free, распределитель может только выпускать страницы в ОС, если вы просто оказались freeing последний живой объект в отображении (или на последних N страницах сегмента данных). Ваше приложение не может разумно предсказать это или даже обнаружить, что это произошло заранее.

CPython делает это еще более сложным-он имеет пользовательский 2-уровневый распределитель объектов поверх пользовательского распределителя памяти поверх malloc. (См.Комментарии Источник для более подробного объяснения.) И кроме того, даже на уровне C API, а тем более Python, вы даже не контролируете напрямую, когда объекты верхнего уровня освобождаются.

Итак, когда вы выпускаете объект, как вы знаете, будет ли он освобождать память для ОС? Ну, во-первых, вы должны знать, что вы выпустили последнюю ссылку (в том числе любые внутренние ссылки, о которых вы не знали), что позволяет GC освободить его. (В отличие от других реализаций, по крайней мере CPython освободит объект, как только это будет разрешено.) Это обычно освобождает по крайней мере две вещи на следующем уровне вниз (например, для строки, вы выпускаете PyString объект и строковый буфер).

если вы do освободить объект, чтобы узнать, приводит ли это к следующему уровню вниз, чтобы освободить блок хранения объектов, вы должны знать внутреннее состояние распределителя объектов, а также то, как он реализован. (Это, очевидно, не может произойти, если вы не освобождаете последнюю вещь в блоке, и даже тогда это может не произойти.)

если вы do освободите блок хранения объектов, чтобы узнать, вызывает ли это free вызов, вы должны знать внутреннее состояние распределителя PyMem, а также как он реализован. (Опять же, вы должны освободить последний используемый блок в пределах malloced регион, и даже тогда, это может не произойти.)

если вы dofree a malloced регион, чтобы узнать, вызывает ли это munmap или эквивалент (или brk), вы должны знать внутреннее состояние malloc, а также как это реализовано. И этот, в отличие от других, очень специфичен для платформы. (И опять же, вы, как правило, должны освобождать последний в использовании malloc внутри mmap сегмент, и даже тогда, он не может случаться.)

Итак, если вы хотите понять, почему это произошло, чтобы освободить ровно 50,5 Мб, вам придется отслеживать его снизу вверх. Почему же malloc unmap 50.5 mb стоит страниц, когда вы сделали эти один или несколько free звонки (наверное, немного больше, чем 50.5 Мб)? Вы должны были бы прочитать вашу платформу malloc, а затем ходить по различным таблицам и спискам, чтобы увидеть его текущее состояние. (На некоторых платформах он может даже использовать информацию системного уровня, которая в значительной степени невозможно захватить, не сделав снимок системы для проверки в автономном режиме, но, к счастью, это обычно не проблема.) И затем вы должны сделать то же самое на 3 уровня выше.

Итак, единственный полезный ответ на вопрос "что."

если вы не занимаетесь разработкой с ограниченными ресурсами (например, встроенной), у вас нет причин заботиться об этих деталях.

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