Освобождение памяти в Python
у меня есть несколько вопросов относительно использования памяти в следующем примере.
-
если я запускаю в интерпретаторе,
foo = ['bar' for _ in xrange(10000000)]
реальная память, используемая на моей машине, доходит до
80.9mb
. Я тогда,del foo
реальная память идет вниз, но только до
30.4mb
. Интерпретатор использует4.4mb
базовая линия так в чем же преимущество в том, чтобы не выпускать26mb
памяти для ОС? Это потому, что Python "планирует вперед", думая, что вы можете использовать опять столько воспоминаний? почему он выпускает
50.5mb
в частности - какова сумма, которая выделяется на основе?есть ли способ заставить Python освободить всю память, которая использовалась (если вы знаете, что больше не будете использовать столько памяти)?
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.xlong
, которые не реализовать 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 результатов, вы даже не заметите этого, но если вы отправляете и получаете большие суммы данные, вы хотите использовать какой-то другой механизм (файл,
mmap
ped или иначе; 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
, распределитель может только выпускать страницы в ОС, если вы просто оказалисьfree
ing последний живой объект в отображении (или на последних N страницах сегмента данных). Ваше приложение не может разумно предсказать это или даже обнаружить, что это произошло заранее.CPython делает это еще более сложным-он имеет пользовательский 2-уровневый распределитель объектов поверх пользовательского распределителя памяти поверх
malloc
. (См.Комментарии Источник для более подробного объяснения.) И кроме того, даже на уровне C API, а тем более Python, вы даже не контролируете напрямую, когда объекты верхнего уровня освобождаются.Итак, когда вы выпускаете объект, как вы знаете, будет ли он освобождать память для ОС? Ну, во-первых, вы должны знать, что вы выпустили последнюю ссылку (в том числе любые внутренние ссылки, о которых вы не знали), что позволяет GC освободить его. (В отличие от других реализаций, по крайней мере CPython освободит объект, как только это будет разрешено.) Это обычно освобождает по крайней мере две вещи на следующем уровне вниз (например, для строки, вы выпускаете
PyString
объект и строковый буфер).если вы do освободить объект, чтобы узнать, приводит ли это к следующему уровню вниз, чтобы освободить блок хранения объектов, вы должны знать внутреннее состояние распределителя объектов, а также то, как он реализован. (Это, очевидно, не может произойти, если вы не освобождаете последнюю вещь в блоке, и даже тогда это может не произойти.)
если вы do освободите блок хранения объектов, чтобы узнать, вызывает ли это
free
вызов, вы должны знать внутреннее состояние распределителя PyMem, а также как он реализован. (Опять же, вы должны освободить последний используемый блок в пределахmalloc
ed регион, и даже тогда, это может не произойти.)если вы do
free
amalloc
ed регион, чтобы узнать, вызывает ли этоmunmap
или эквивалент (илиbrk
), вы должны знать внутреннее состояниеmalloc
, а также как это реализовано. И этот, в отличие от других, очень специфичен для платформы. (И опять же, вы, как правило, должны освобождать последний в использованииmalloc
внутриmmap
сегмент, и даже тогда, он не может случаться.)Итак, если вы хотите понять, почему это произошло, чтобы освободить ровно 50,5 Мб, вам придется отслеживать его снизу вверх. Почему же
malloc
unmap 50.5 mb стоит страниц, когда вы сделали эти один или несколькоfree
звонки (наверное, немного больше, чем 50.5 Мб)? Вы должны были бы прочитать вашу платформуmalloc
, а затем ходить по различным таблицам и спискам, чтобы увидеть его текущее состояние. (На некоторых платформах он может даже использовать информацию системного уровня, которая в значительной степени невозможно захватить, не сделав снимок системы для проверки в автономном режиме, но, к счастью, это обычно не проблема.) И затем вы должны сделать то же самое на 3 уровня выше.Итак, единственный полезный ответ на вопрос "что."
если вы не занимаетесь разработкой с ограниченными ресурсами (например, встроенной), у вас нет причин заботиться об этих деталях.
и если are делать развитие с ограниченными ресурсами, зная эти детали бесполезны; вы в значительной степени должны сделать конец-бегать по всем этим уровням и конкретно
mmap
память, которая вам нужна на уровне приложения (возможно, с одним простым, хорошо понятным, специфичным для приложения распределителем зон между ними).