Python очистить оперативную память

Обновлено: 01.07.2024

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

если я бегу в интерпретаторе,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В этом руководстве мы узнаем, как Python управляет памятью или как обрабатывает наши данные внутренне. Это руководство даст глубокое понимание основ управления памятью Python. Когда мы выполняем наш скрипт, в его памяти запускается много логических процессов, чтобы сделать код эффективным.

Вступление

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

В Python памятью управляет менеджер Python, который определяет, куда поместить данные приложения в память.

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

С другой стороны, когда данные больше не используются, они могут быть удалены менеджером памяти Python. Но вопрос в том, как? А откуда взялось это воспоминание?

Распределение памяти Python

  • Распределение статической памяти
  • Распределение динамической памяти

Статической памяти

Размещение стека

Структура данных Stack используется для хранения статической памяти. Это необходимо только внутри конкретной функции или вызова метода. Функция добавляется в стек вызовов программы всякий раз, когда мы ее вызываем. Назначение переменной внутри функции временно сохраняется в стеке вызова функции; функция возвращает значение, и стек вызовов переходит к текстовой задаче. Компилятор обрабатывает все эти процессы, поэтому нам не нужно об этом беспокоиться.

Стек вызовов(структура данных стека) содержит рабочие данные программы, такие как подпрограммы или вызовы функций, в порядке их вызова. Эти функции выходят из стека при вызове.

Динамической памяти

Как мы знаем, все в Python является объектом, а это означает, что динамическое распределение памяти вдохновляет управление памятью Python. Диспетчер памяти Python автоматически исчезает, когда объект больше не используется.

Распределение памяти в куче

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

В приведенном ниже примере мы определяем, как переменная функции хранится в стеке и куче.

Реализация Python по умолчанию

В основном язык Python написан на английском языке. Однако это определено в справочном руководстве, что само по себе бесполезно. Итак, нам нужен код, основанный на интерпретаторе правила в руководстве.

Преимущество реализации по умолчанию заключается в том, что она выполняет код Python на компьютере, а также преобразует наш код Python в инструкции. Итак, мы можем сказать, что реализация Python по умолчанию удовлетворяет обоим требованиям.

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

Сборщик мусора Python

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

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

Как мы знаем, Python использует динамическое выделение памяти, которым управляет структура данных Heap. Куча памяти содержит объекты и другие структуры данных, которые будут использоваться в программе. Диспетчер памяти Python управляет выделением или отменой выделения пространства кучи с помощью функций API.

Объекты Python в памяти

Как мы знаем, в Python все является объектом. Объект может быть простым(содержащий числа, строки и т. д.) или контейнером(словарь, списки или определенные пользователем классы). В Python нам не нужно объявлять переменные или их типы перед их использованием в программе.

Давайте разберемся в следующем примере.

Как видно из вышеприведенного вывода, мы присвоили значение объекту x и распечатали его. Когда мы удаляем объект x и пытаемся получить доступ в дальнейшем коде, будет ошибка, утверждающая, что переменная x не определена.

Следовательно, сборщик мусора Python работает автоматически, и программистам не нужно беспокоиться об этом, в отличие от C.

Подсчет ссылок в Python

Подсчет ссылок показывает, сколько раз другие объекты ссылаются на данный объект. Когда назначается ссылка на объект, счетчик объектов увеличивается на единицу. Когда ссылки на объект удаляются, количество объектов уменьшается. Когда счетчик ссылок становится равным нулю, диспетчер памяти Python выполняет освобождение. Давайте сделаем это простым для понимания.

Предположим, есть две или более переменных, которые содержат одно и то же значение, поэтому виртуальная машина Python скорее создает другой объект с таким же значением в частной куче. Фактически это указывает на то, что вторая переменная указывает на изначально существующее значение в частной куче.

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

Подсчет ссылок

Когда мы присваиваем значение переменной x, целочисленный объект 10 создается в памяти кучи, и его ссылка присваивается x.

y = x

В приведенном выше коде мы присвоили y = x, что означает, что объект y будет ссылаться на тот же объект, потому что Python выделил ту же ссылку на объект новой переменной, если объект уже существует с тем же значением.

Теперь посмотрим на другой пример.

Новый объект ссылки

Переменные x и y не ссылаются на один и тот же объект, потому что x увеличивается на единицу, x создает новый объект ссылки, а y по-прежнему ссылается на 10.

Преобразование сборщика мусора

Сборщик мусора Python классифицировал объекты с помощью своей генерации. Сборщик мусора Python имеет три поколения. Когда мы определяем новый объект в программе, его жизненный цикл обрабатывается первым поколением сборщика мусора. Если объект используется в другой программе, он будет стимулироваться до следующего поколения. У каждого поколения есть порог.

Сборщик мусора вступает в действие, если превышен порог количества выделений за вычетом числа отмененных.

Мы можем изменить пороговое значение вручную с помощью модуля GC. Этот модуль предоставляет метод get_threshold() для проверки порогового значения другого поколения сборщика мусора. Давайте разберемся в следующем примере.

Пороговое значение для запуска сборщика мусора можно изменить с помощью метода set_threshold().

В приведенном выше примере значение порога увеличилось для всех трех поколений. Это повлияет на частоту запуска сборщика мусора. Программистам не нужно беспокоиться о сборщике мусора, но он играет важную роль в оптимизации среды выполнения Python для целевой системы.

Сборщик мусора Python обрабатывает низкоуровневые детали для разработчика.

Важность выполнения ручной сборки мусора

Как мы обсуждали ранее, интерпретатор Python обрабатывает ссылку на объект, используемый в программе. Он автоматически освобождает память, когда счетчик ссылок становится равным нулю. Это классический подход для подсчета ссылок, если он не работает, когда программа ссылается на циклы. Цикл ссылок происходит, когда один или несколько объектов ссылаются друг на друга. Следовательно, счетчик ссылок никогда не становится нулевым.

Мы создали справочный цикл. Объект list1 ссылается на сам объект list1. Когда функция возвращает объект list1, память для объекта list1 не освобождается. Так что подсчет ссылок не подходит для решения ссылочного цикла. Но мы можем решить эту проблему, изменив сборщик мусора или производительность сборщика мусора.

Для этого мы будем использовать функцию gc.collect() для модуля gc.

Приведенный выше код даст количество собранных и освобожденных объектов.

Метод gc.collect() используется для выполнения сборки мусора по времени. Этот метод вызывается через фиксированный интервал времени для выполнения сборки мусора на основе времени.

При четной сборке мусора функция gc.collect() вызывается после возникновения события. Давайте разберемся в следующем примере.

В приведенном выше коде мы создали объект list1, на который ссылается переменная list. Первый элемент объекта списка ссылается на себя. Счетчик ссылок на объект списка всегда больше нуля, даже если он удален или выходит за рамки программы.

Управление памятью в Python

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

Python использует часть памяти для внутреннего использования и необъектную память. Другая часть памяти используется для объекта Python, такого как int, dict, list и т. д.

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

Стратегия выделения памяти CPython состоит из трех основных компонентов.

Распространенные способы уменьшить сложность пространства

Мы можем следовать некоторым передовым методам, чтобы уменьшить сложность пространства. Эти методы должны сэкономить достаточно места и сделать программу эффективной. Ниже приведены несколько практик в Python для распределителей памяти.

Мы определяем список на Python; распределитель памяти распределяет память кучи соответственно индексации списка. Предположим, нам нужен подсписок к данному списку, затем мы должны выполнить нарезку списка. Это простой способ получить подсписок из исходного списка. Он подходит для небольшого количества данных, но не подходит для емких данных.

Следовательно, нарезка списка генерирует копии объекта в списке, просто копирует ссылку на них. В результате распределитель памяти Python создает копию объекта и выделяет ее. Поэтому нам нужно избегать разрезания списка.

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

Разработчик должен попытаться использовать «для элемента в массиве» вместо «для индекса в диапазоне(len(array))» для экономии места и времени. Если программе не нужна индексация элемента списка, не используйте ее.

Конкатенация строк не подходит для экономии места и времени. По возможности, мы должны избегать использования знака «+» для конкатенации строк, потому что строки неизменяемы. Когда мы добавляем новую строку к существующей строке, Python создает новую строку и присваивает ее новому адресу.

Каждая строка требует фиксированного размера памяти в зависимости от символа и его длины. Когда мы меняем строку, ей требуется другой объем памяти и ее необходимо перераспределить.

Мы создали переменную a для ссылки на строковый объект, который является информацией о строковом значении.

Затем мы добавляем новую строку с помощью оператора «+». Python перераспределяет новую строку в памяти в зависимости от ее размера и длины. Предположим, что размер памяти исходной строки равен n байтам, тогда новая строка будет размером m байтов.

Вместо использования конкатенации строк мы можем использовать «.join(iterable_object)» или формат или%. Это оказывает огромное влияние на экономию памяти и времени.

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

В следующем примере мы реализуем итератор, который вызывает специальную функцию генератора. Ключевое слово yield возвращает текущее значение, переходя к следующему значению только на следующей итерации цикла.

  • По возможности используйте встроенную библиотеку

Если мы используем методы, которые уже были предопределены в библиотеке Python, импортируйте соответствующую библиотеку. Это сэкономит много места и времени. Мы также можем создать модуль, чтобы определить функцию и импортировать ее в текущую рабочую программу.

Заключение

Я пытаюсь выполнить итерацию более 100 000 изображений, захватить некоторые функции изображения и сохранить полученный фрейм данных на диске в виде файла маринада.

К сожалению, из-за ограничений ОЗУ я вынужден разделить изображения на 20 000 кусков и выполнить операции с ними перед сохранением результатов на диск.

Предполагается, что код, написанный ниже, сохранит фрейм данных результатов для 20 000 изображений перед началом цикла обработки следующих 20000 изображений.

Однако - похоже, это не решает мою проблему, так как память не освобождается из ОЗУ в конце первого цикла for

Так что где-то при обработке 50 000-й записи программа вылетает из-за ошибки Out of Memory.

Я попытался удалить объекты после сохранения их на диск и вызова сборщика мусора, однако использование оперативной памяти, похоже, не снижается.

Что мне не хватает?

Теперь, может быть, что-то в 50 000-м очень большое, и это вызывает OOM, поэтому, чтобы проверить это, я сначала попробую:

Если он потерпит неудачу при 10000, это подтвердит, является ли 20КБ слишком большим для размера фрагмента, или если он снова потерпит неудачу при 50000, есть проблема с кодом .

Во-первых, вам не нужен явный конструктор list , в python гораздо лучше выполнять итерации, а не генерировать весь список в памяти.

Я думаю, что вы можете неправильно использовать ThreadPool здесь:

Предотвращает дальнейшую отправку задач в пул. Как только все задачи будут выполнены, рабочие процессы завершатся.

Это выглядит так, как будто close может иметь некоторые мысли, которые все еще работают, хотя я думаю, что это безопасно, это выглядит немного непитонным, лучше использовать менеджер контекста для ThreadPool:

Вы должны собрать после объединения / после с:

Вы также можете попробовать разделить это на более мелкие части, например. 10000 или даже меньше!

Молоток 1

Одна вещь, которую я хотел бы сделать здесь, вместо того, чтобы использовать pandas DataFrames и большие списки, это использовать базу данных SQL, вы можете сделать это локально с помощью sqlite3:

И используйте менеджер контекста:

Таким образом, нам не придется обрабатывать объекты большого списка или DataFrame.

Вы можете передать соединение каждому из потоков . у вас может быть что-то немного странное, например:

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

Молоток 2

Используйте здесь подпроцесс, вместо того, чтобы запускать его в одном и том же экземпляре python, «shell» для другого.

Поскольку вы можете передать начало и конец python как sys.argv, вы можете нарезать их:

Таким образом, подпроцесс должным образом очистит python (не будет никаких утечек памяти, так как процесс будет остановлен).

Бьюсь об заклад, что Hammer 1 - это то, что нужно, такое ощущение, что вы склеиваете много данных и без необходимости считываете их в списки python, а использование sqlite3 (или другой базы данных) полностью избегает этого.

Короче говоря, вы не можете освободить память обратно в интерпретаторе Python. Лучше всего было бы использовать многопроцессорность, поскольку каждый процесс может обрабатывать память самостоятельно.

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

Кажется, что обработка изображений идемпотентна и атомарна, поэтому это может быть задание на сельдерей .

Вы можете запустить несколько работников, которые будут Процесс задания - работа с изображением.

Кроме того, он имеет конфигурацию. на утечки памяти.

НЕ вызывайте list (), это создает список в памяти того, что возвращается из div_chunks (). Вот где, вероятно, возникает проблема с памятью.

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

Пожалуйста, опубликуйте трассировку стека, чтобы у нас было больше информации

Мое решение таких проблем - использовать какой-то инструмент параллельной обработки. Я предпочитаю joblib, поскольку он позволяет распараллеливать даже локально созданные функции (которые являются "деталями реализации" "и поэтому лучше не делать их глобальными в модуле). Мой другой совет: не используйте потоки (и пулы потоков) в Python, используйте процессы (и пулы процессов) - это почти всегда лучшая идея! Просто убедитесь, что в joblib создан пул как минимум из 2 процессов, в противном случае он будет запускать все в исходном процессе python, и в результате ОЗУ не будет освобождено. Как только рабочие процессы joblib закрываются автоматически, оперативная память, которую они выделяют, будет полностью освобождена ОС. Моим любимым оружием на выбор является joblib.Parallel. Если вам нужно передать работникам большие данные (например, размером более 2 ГБ), используйте joblib.dump (для записи объекта python в файл в основном процессе) и joblib.load (чтобы прочитать его в рабочем процессе).

About del object : в python команда фактически не удаляет объект. Это только уменьшает его счетчик ссылок. Когда вы запускаете import gc; gc.collect() , сборщик мусора сам решает, какую память освободить, а какую оставить выделенной, и я не знаю, как заставить его освободить всю возможную память. Еще хуже, если бы некоторая память была фактически выделена не python, а вместо этого, например, в каком-то внешнем коде C / C ++ / Cython / etc, и код не связывал счетчик ссылок python с памятью, вы бы абсолютно ничего не сделали может сделать это, чтобы освободить его из Python, кроме того, что я написал выше, то есть, завершив процесс Python, который выделил ОЗУ, и в этом случае он будет гарантированно освобожден ОС. Вот почему единственный надежный способ на 100% освободить память в python - это запустить код, который выделяет ее в параллельном процессе, а затем завершить процесс .

pd.DataFrame(. ) может течь в некоторых сборках linux (см. Проблему с github и "обходной путь"), поэтому даже del df может не помочь.

В вашем случае решение от github может быть использовано без патчей pd.DataFrame.__del__ :

Постскриптум Это решение не поможет, если какой-либо один кадр данных слишком велик. Этому можно помочь только уменьшив CHUNK_SIZE

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

Я бы реорганизовал ваш код примерно так:

А затем я бы изменил функцию get_image_features , добавив (что-то вроде) эти две строки в конец. Я не могу сказать, как именно вы обрабатываете эти изображения, но идея состоит в том, чтобы сделать каждое изображение внутри каждого процесса, а затем сразу же сохранить его на диск:

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

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

Пользователю не нужно беспокоиться об управлении памятью, поскольку процесс выделения и удаления памяти полностью автоматизирован. Восстановленная память может использоваться другими объектами.

Python Garbage Collection (Уборка мусора)

Как объяснялось ранее, Python удаляет объекты, на которые больше нет ссылок в программе, чтобы освободить пространство памяти. Этот процесс, в котором Python освобождает блоки памяти, которые больше не используются, называется Garbage Collection. Python Garbage Collector (GC) работает во время выполнения программы и запускается, если счетчик ссылок уменьшается до нуля. Счетчик ссылок увеличивается, если объекту присваивается новое имя или он помещается в контейнер, такой как кортеж или словарь. Аналогично, счетчик ссылок уменьшается, когда ссылка на объект переназначается, когда ссылка на объект выходит из области видимости или когда объект удаляется.

Примеры, когда количество ссылок увеличивается:

  • оператор присваивания
  • передача аргументов
  • вставка нового объекта в лист (увеличивается количество ссылок для объекта)
  • конструция вида foo = bar (foo начинается ссылаться на тот же объект, что и bar)

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

Объекты Python в памяти

Каждая переменная в Python действует как объект. Объекты могут быть простыми (содержащими числа, строки и т. д.) Или контейнерами (словарями, списками или пользовательскими классами). Кроме того, Python является динамически типизированным языком, что означает, что нам не нужно объявлять переменные или их типы перед их использованием в программе.

Если вы посмотрите на первые 2 строки вышеуказанной программы, объект x известен. Если мы удалим объект x и пытаемся его использовать, мы получим ошибку, в которой говорится, что переменная x не определена.

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

Модификация Garbage Collector

Более ранние поколения также собирают мусор чаще, чем высшие поколения. Это связано с тем, что более новые объекты чаще отбрасываются, чем старые.

Модуль gc включает функции для изменения порогового значения, ручного запуска процесса сбора мусора, отключения процесса сбора мусора и т. д. Мы можем проверить пороговые значения разных поколений сборщика мусора с помощью метода get_threshold():

Пример вывода:

Как видите, здесь у нас есть порог 700 для первого поколения и 10 для каждого из двух других поколений.

Мы можем изменить пороговое значение для запуска процесса сбора мусора, используя метод set_threshold() модуля gc:

В приведенном выше примере мы увеличили пороговое значение для всех 3 поколений. Увеличение порогового значения уменьшит частоту работы сборщика мусора. Обычно нам не нужно слишком много думать о сборке мусора в Python, но это может быть полезно при оптимизации времени выполнения программ для вашей целевой системы. Одним из ключевых преимуществ является то, что механизм сборки мусора в Python автоматически обрабатывает множество низкоуровневых деталей.

Зачем выполнять сборку мусора вручную?

Мы знаем, что интерпретатор Python отслеживает ссылки на объекты, используемые в программе. В более ранних версиях Python (до версии 1.6) интерпретатор Python использовал только механизм подсчета ссылок для обработки памяти. Когда количество ссылок падает до нуля, интерпретатор Python автоматически освобождает память. Этот классический механизм подсчета ссылок очень эффективен, за исключением того, что он не работает, когда в программе есть циклические ссылок. Зацикливание ссылок происходит, если один или несколько объектов ссылаются друг на друга, и, следовательно, счетчик ссылок никогда не достигает нуля.

Давайте рассмотрим пример.

Приведенный выше код создает ссылочный цикл, где объект list ссылается на себя. Следовательно, память для объект list не будет освобождена автоматически, когда завершится выполнение функции. Проблема зацикливания не может быть решена путем подсчета ссылок. Однако эту проблему можно решить, изменив поведение сборщика мусора в вашем приложении Python.

Для этого мы можем использовать функцию gc.collect() модуля gc.

gc.collect() возвращает количество объектов, которые были собраны и удалены.

Существует два способа выполнения сборки мусора вручную: сборка мусора на основе времени или события.

Основанная на времени сборка мусора довольно проста: функция gc.collect() вызывается через фиксированный интервал времени.

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

Давайте разберемся с ручной сборкой мусора, создав несколько циклов.

На экране отобразится следующее:

Приведенный выше сценарий создает объект list, на который ссылается переменная с творческим именем list. Первый элемент объекта списка ссылается сам на себя. Счетчик ссылок объекта list всегда больше нуля, даже если он удален или находится вне области действия программы. Следовательно, объект list не обрабатывается сборщиком мусора из-за циклической ссылки.

В приведенном выше коде, поскольку число ссылок равно по крайней мере 1 и никогда не может достигнуть 0, мы принудительно собирали объекты с garbage collected, вызывая gc.collect(). Тем не менее, помните не форсируйте сборку мусора слишком часто. Причина в том, что даже после освобождения памяти GC тратит время на оценку пригодности объекта для сбора мусора, занимая процессорное время и ресурсы. Кроме того, не забудьте вручную управлять сборщиком мусора только после того, как ваше приложение полностью запустится.

Заключение

В этой статье мы обсудили, как управление памятью в Python обрабатывается автоматически с помощью стратегий подсчета ссылок и сбора мусора. Без garbage collection реализация успешного механизма управления памятью в Python невозможна. Кроме того, программистам не нужно беспокоиться об удалении выделенной памяти, так как об этом заботится менеджер памяти Python. Это приводит к уменьшению утечек памяти и повышению производительности.

Читайте также: