Понимаем, сколько памяти используют ваши объекты Python
Оказывается, нетривиально выяснить, сколько памяти фактически потребляется. В этой статье я расскажу о тонкостях управления памятью объекта Python и покажу, как точно измерить потребляемую память.
Также я запустил числа на 64-битном Python 2.7. В Python 3 числа иногда немного отличаются (особенно для строк, которые всегда являются Unicode), но концепции одинаковы.
Практическое исследование использования памяти Python
Во-первых, давайте немного разберемся и получим конкретное представление о фактическом использовании памяти объектами Python.
Встроенная функция sys.getsizeof()
Модуль sys стандартной библиотеки предоставляет функцию getsizeof(). Эта функция принимает объект (и необязательный параметр по умолчанию), вызывает метод sizeof() объекта и возвращает результат, поэтому вы также можете сделать ваши объекты инспектируемыми.
Измерение памяти объектов Python
Давайте начнем с некоторых числовых типов:
Интересно. Целое число занимает 24 байта.
Вот это да. 80 байтов! Это действительно заставляет задуматься о том, хотите ли вы представлять большое количество вещественных чисел как числа с плавающей запятой или десятичные дроби.
Давайте перейдем к строкам и коллекциям:
Хорошо. Пустая строка занимает 37 байтов, и каждый дополнительный символ добавляет еще один байт. Это многое говорит о компромиссе между сохранением нескольких коротких строк, когда вы будете платить 37 байтов за каждую, а не одну длинную строку, где вы платите только один раз.
Строки Unicode ведут себя аналогично, за исключением того, что служебные данные составляют 50 байтов, и каждый дополнительный символ добавляет 2 байта. Это стоит учитывать, если вы используете библиотеки, которые возвращают строки Unicode, но ваш текст может быть представлен в виде простых строк.
Кстати, в Python 3 строки всегда имеют Unicode, а служебные данные составляют 49 байт (они где-то сохранили байт). Объект байтов имеет служебную информацию только 33 байта. Если у вас есть программа, которая обрабатывает много коротких строк в памяти, и вы заботитесь о производительности, рассмотрите Python 3.
В чем дело? Пустой список занимает 72 байта, но каждый дополнительный int добавляет всего 8 байтов, где размер int составляет 24 байта. Список, который содержит длинную строку, занимает всего 80 байтов.
Ответ прост. Список не содержит сами объекты int. Он просто содержит 8-байтовый (в 64-битных версиях CPython) указатель на фактический объект int. Это означает, что функция getsizeof() не возвращает фактическую память списка и всех объектов, которые он содержит, а только память списка и указатели на свои объекты. В следующем разделе я представлю функцию deep_getsizeof(), которая решает эту проблему.
Наборы и словари якобы вообще не растут при добавлении элементов, но отмечают огромные накладные расходы.
Суть в том, что у объектов Python огромные фиксированные накладные расходы. Если ваша структура данных состоит из большого количества объектов коллекций, таких как строки, списки и словари, которые содержат небольшое количество элементов каждый, вы много платите.
Функция deep_getsizeof()
Теперь, когда я напугал вас до полусмерти и продемонстрировал, что sys.getsizeof() может только сказать вам, сколько памяти занимает примитивный объект, давайте посмотрим на более адекватное решение. Функция deep_getsizeof() рекурсивно выполняет детализацию и вычисляет фактическое использование памяти графом объектов Python.
У этой функции есть несколько интересных аспектов. Она учитывает объекты, на которые ссылаются несколько раз, и учитывает их только один раз, отслеживая идентификаторы объектов. Другая интересная особенность реализации заключается в том, что она в полной мере использует абстрактные базовые классы модуля коллекций. Это позволяет функции очень лаконично обрабатывать любую коллекцию, которая реализует базовые классы Mapping или Container, вместо непосредственного обращения к множеству типов коллекций, таких как: строка, Unicode, байты, список, кортеж, dict, frozendict, OrderedDict, set, frozenset и т.д.
Давайте посмотрим на это в действии:
Строка длиной 7 занимает 44 байта (37 служебных данных + 7 байтов для каждого символа).
Пустой список занимает 72 байта (только накладные расходы).
python deep_getsizeof ([x], set ()) 124
Список, содержащий строку x, занимает 124 байта (72 + 8 + 44).
Список, содержащий строку x 5 раз, занимает 156 байтов (72 + 5 * 8 + 44).
Последний пример показывает, что deep_getsizeof() подсчитывает ссылки на один и тот же объект (строку x) только один раз, но подсчитывается указатель каждой ссылки.
Баг или фича
Оказывается, что у CPython есть несколько хитростей, поэтому числа, которые вы получаете от deep_getsizeof(), не полностью отражают использование памяти программой Python.
Подсчет ссылок
Python управляет памятью, используя семантику подсчета ссылок. Когда на объект больше не ссылаются, его память освобождается. Но пока есть ссылка, объект не будет освобожден. Такие вещи, как циклические ссылки, могут вас сильно укусить.
Маленькие объекты
CPython управляет небольшими объектами (менее 256 байтов) в специальных пулах на 8-байтовых границах. Есть пулы для 1-8 байтов, 9-16 байтов и вплоть до 249-256 байтов. Когда объект размером 10 выделяется, он выделяется из 16-байтового пула для объектов размером 9-16 байт. Таким образом, хотя он содержит только 10 байтов данных, он будет стоить 16 байтов памяти. Если вы выделяете 1 000 000 объектов размером 10, вы фактически используете 16 000 000 байтов, а не 10 000 000 байтов, как вы можете предположить. Эти 60% накладных расходов явно не тривиальны.
Целые числа
CPython хранит глобальный список всех целых чисел в диапазоне [-5, 256]. Эта стратегия оптимизации имеет смысл, потому что маленькие целые числа всплывают повсюду, и, учитывая, что каждое целое число занимает 24 байта, оно экономит много памяти для типичной программы.
Это также означает, что CPython предварительно выделяет 266 * 24 = 6384 байта для всех этих целых чисел, даже если вы не используете большинство из них. Вы можете проверить это с помощью функции id(), которая дает указатель на фактический объект. Если вы называете id(x) несколько для любого x в диапазоне [-5, 256], вы будете каждый раз получать один и тот же результат (для одного и того же целого числа). Но если вы попробуете это для целых чисел за пределами этого диапазона, каждый из них будет отличаться (новый объект создается на лету каждый раз).
Вот несколько примеров в этом диапазоне:
Вот несколько примеров за пределами диапазона:
Память Python против системной памяти
CPython является своего рода притяжательным. Во многих случаях, когда на объекты памяти в вашей программе больше не ссылаются, они не возвращаются в систему (например, маленькие объекты). Это хорошо для вашей программы, если вы выделяете и освобождаете много объектов (которые принадлежат одному и тому же 8-байтовому пулу), потому что Python не должен беспокоить систему, что относительно дорого. Но это не так здорово, если ваша программа обычно использует X байтов и при некоторых временных условиях она использует в 100 раз больше (например, анализирует и обрабатывает большой файл конфигурации только при запуске).
Теперь эта память 100X может быть бесполезно захвачена в вашей программе, никогда больше не использоваться и лишать систему возможности выделять ее другим программам. Ирония заключается в том, что если вы используете модуль обработки для запуска нескольких экземпляров вашей программы, вы строго ограничите количество экземпляров, которые вы можете запустить на данном компьютере.
Профилировщик памяти
Чтобы измерить и измерить фактическое использование памяти вашей программой, вы можете использовать модуль memory_profiler. Я немного поиграл с этим, и я не уверен, что доверяю результатам. Он очень прост в использовании. Вы декорируете функцию (может быть главной (0 функция)) с помощью декоратора @profiler, и когда программа завершает работу, профилировщик памяти выводит на стандартный вывод удобный отчет, который показывает общее количество и изменения в памяти для каждой строки. Вот пример программы, которую я запускал под профилировщиком:
Заключение
CPython использует много памяти для своих объектов. Он использует различные приемы и оптимизации для управления памятью. Отслеживая использование памяти вашим объектом и зная модель управления памятью, вы можете значительно уменьшить объем памяти вашей программы.
Как определить размер объекта в Python?
Я хочу знать, как получить размер объектов, таких как строка, целое число и т. Д. В Python.
Я использую файл XML, который содержит поля размера, которые определяют размер значения. Я должен разобрать этот XML и сделать свое кодирование. Когда я хочу изменить значение определенного поля, я проверю поле размера этого значения. Здесь я хочу сравнить, имеет ли новое значение, которое я собираюсь ввести, такой же размер, как в XML. Мне нужно проверить размер нового значения. В случае строки я могу сказать ее длину. Но в случае int, float и т. Д. Я запутался.
Просто используйте функцию sys.getsizeof, определенную в sys модуле.
Вернуть размер объекта в байтах. Объект может быть любым типом объекта. Все встроенные объекты будут возвращать правильные результаты, но это не должно выполняться для сторонних расширений, поскольку это зависит от реализации.
getsizeof вызывает метод объекта __sizeof__ и добавляет дополнительные издержки сборщика мусора, если объектом управляет сборщик мусора.
Пример использования в python 3.0:
Как определить размер объекта в Python?
Ответ «Просто используйте sys.getsizeof» не является полным ответом.
Более полный ответ
Используя 64-битный Python 3.6 из дистрибутива Anaconda, с помощью sys.getsizeof, я определил минимальный размер следующих объектов и обратите внимание, что устанавливает и диктует предварительное выделение пространства, поэтому пустые не увеличиваются снова до истечения заданного количества (что может зависит от реализации языка):
Как вы это интерпретируете? Хорошо, скажем, у вас есть набор из 10 предметов. Если каждый элемент имеет размер 100 байт, то насколько велика вся структура данных? Сам набор равен 736, потому что его размер увеличился до 736 байт. Затем вы добавляете размер элементов, так что всего получается 1736 байт.
Некоторые предостережения для определений функций и классов:
Обратите внимание, что каждое определение класса имеет структуру прокси __dict__ (48 байт) для атрибутов класса. У каждого слота есть дескриптор (например, a property ) в определении класса.
Временные интервалы начинаются с 48 байтов в первом элементе и увеличиваются на 8 каждый. Только пустые объекты со слотами имеют 16 байтов, и экземпляр без данных имеет очень мало смысла.
Также обратите внимание, что мы используем это, sys.getsizeof() потому что мы заботимся об использовании предельного пространства, которое включает в себя накладные расходы на сборку мусора для объекта, из документов :
getsizeof () вызывает метод объекта __sizeof__ и добавляет дополнительные издержки сборщика мусора, если объектом управляет сборщик мусора.
Также обратите внимание, что изменение размеров списков (например, повторное добавление к ним) заставляет их предварительно распределять пространство, аналогично наборам и диктам. Из исходного кода listobj.c :
Исторические данные
Анализ Python 2.7, подтвержденный guppy.hpy и sys.getsizeof :
Обратите внимание, что словари ( но не наборы ) получили более компактное представление в Python 3.6
Я думаю, что 8 байтов на каждый элемент для ссылки имеют большой смысл на 64-битной машине. Эти 8 байтов указывают на место в памяти, в котором находится содержащийся элемент. 4 байта имеют фиксированную ширину для юникода в Python 2, если я правильно помню, но в Python 3 str становится юникодом ширины, равной максимальной ширине символов.
Более полная функция
Мы хотим положиться на gc.get_referents этот поиск, потому что он работает на уровне C (что делает его очень быстрым). Недостатком является то, что get_referents может возвращать избыточные члены, поэтому мы должны убедиться, что мы не удваиваем счет.
Мы собираемся использовать черный список типов, поэтому мы не включаем всю программу в наш счетчик размеров.
Например, функции знают достаточно много о модулях, в которых они созданы.
Другое отличие состоит в том, что строки, являющиеся ключами в словарях, обычно интернированы, поэтому они не дублируются. Проверка id(key) также позволит нам избежать подсчета дубликатов, что мы и сделаем в следующем разделе. Решение черного списка пропускает подсчет ключей, которые являются строками в целом.
Типы в белых списках, Рекурсивный посетитель (старая реализация)
Функция такого типа дает гораздо более детальный контроль над типами, которые мы собираемся рассчитывать на использование памяти, но есть опасность пропустить типы:
И я проверил это довольно случайно (я должен протестировать это):
Эта реализация разбивает определения классов и определения функций, потому что мы не используем все их атрибуты, но поскольку они должны существовать в процессе только один раз в памяти, их размер на самом деле не имеет большого значения.
Поймите, сколько памяти используют ваши объекты Python
Python — это фантастический язык программирования. Он также известен как довольно медленный, в основном из-за его огромной гибкости и динамических характеристик. Для многих приложений и областей это не проблема из-за их требований и различных методов оптимизации. Менее известно, что графы объектов Python (вложенные словари списков, кортежей и примитивных типов) занимают значительный объем памяти. Это может быть гораздо более серьезным ограничивающим фактором из-за его влияния на кеширование, виртуальную память, многопользовательскую работу с другими программами и в целом более быстрое исчерпание доступной памяти, которая является дефицитным и дорогим ресурсом.
Оказывается, нетривиально выяснить, сколько памяти фактически потребляется. В этой статье я расскажу о тонкостях управления памятью объекта Python и покажу, как точно измерить потребляемую память.
В этой статье я остановлюсь исключительно на CPython — основной реализации языка программирования Python. Эксперименты и выводы здесь не относятся к другим реализациям Python, таким как IronPython, Jython и PyPy.
Также я запустил числа на 64-битном Python 2.7. В Python 3 числа иногда немного отличаются (особенно для строк, которые всегда являются Unicode), но концепции одинаковы.
Практическое исследование использования памяти Python
Сначала давайте немного разберемся и получим конкретное представление о фактическом использовании памяти объектами Python.
Встроенная функция sys.getsizeof ()
Измерение памяти объектов Python
Давайте начнем с некоторых числовых типов:
Интересный. Целое число занимает 24 байта.
python sys.getsizeof(5.3) 24
Хм … float также занимает 24 байта.
python from decimal import Decimal sys.getsizeof(Decimal(5.3)) 80
Вау. 80 байт! Это действительно заставляет задуматься о том, хотите ли вы представлять большое количество вещественных чисел как числа с плавающей запятой или десятичные дроби.
Давайте перейдем к строкам и коллекциям:
« `python sys.getsizeof (») 37 sys.getsizeof (‘1’) 38 sys.getsizeof (‘1234’) 41
sys.getsizeof (u ») 50 sys.getsizeof (u’1 ‘) 52 sys.getsizeof (u’1234’) 58 « `
OK. Пустая строка занимает 37 байтов, и каждый дополнительный символ добавляет еще один байт. Это многое говорит о компромиссе между сохранением нескольких коротких строк, когда вы будете платить 37 байтов за каждую, и одной длинной строкой, где вы платите только один раз.
Строки Unicode ведут себя аналогично, за исключением того, что служебные данные составляют 50 байтов, и каждый дополнительный символ добавляет 2 байта. Это нужно учитывать, если вы используете библиотеки, которые возвращают строки Unicode, но ваш текст может быть представлен в виде простых строк.
Кстати, в Python 3 строки всегда имеют Unicode, а служебные данные составляют 49 байт (они где-то сохранили байт). Объект байтов имеет служебную информацию только 33 байта. Если у вас есть программа, которая обрабатывает много коротких строк в памяти, и вы заботитесь о производительности, рассмотрите Python 3.
python sys.getsizeof([]) 72 sys.getsizeof([1]) 88 sys.getsizeof([1, 2, 3, 4]) 104 sys.getsizeof([‘a long longlong string’])
В чем дело? Пустой список занимает 72 байта, но каждое дополнительное int добавляет всего 8 байтов, где размер int составляет 24 байта. Список, который содержит длинную строку, занимает всего 80 байтов.
Ответ прост. Список не содержит сами объекты int. Он просто содержит 8-байтовый (в 64-битных версиях CPython) указатель на фактический объект int. Это означает, что функция getsizeof () не возвращает фактическую память списка и всех объектов, которые он содержит, а только память списка и указатели на его объекты. В следующем разделе я представлю функцию deep_getsizeof (), которая решает эту проблему.
python sys.getsizeof(()) 56 sys.getsizeof((1,)) 64 sys.getsizeof((1, 2, 3, 4)) 88 sys.getsizeof((‘a long longlong string’,)) 64
История похожа на кортежи. Накладные расходы пустого кортежа составляют 56 байтов против 72 списка. Опять же, эта разница в 16 байтов на последовательность — это низко висящий плод, если у вас есть структура данных с большим количеством небольших неизменяемых последовательностей.
« `python sys.getsizeof (set ()) 232 sys.getsizeof (set ([1)) 232 sys.getsizeof (set ([1, 2, 3, 4])) 232
sys.getsizeof (<>) 280 sys.getsizeof (dict (a = 1)) 280 sys.getsizeof (dict (a = 1, b = 2, c = 3)) 280 « `
Наборы и словари якобы вообще не растут, когда вы добавляете предметы, но обратите внимание на огромные накладные расходы.
Про большой расход памяти в Python
У Python есть множество достоинств, но есть несколько недостатков, которые мешают Python стать действительно вездесущим языков. Один из таких недостатков — это большой расход памяти.
Если вы работаете на современном компьютере и не пишите приложения схожие с GTA 5, то возможно вы никогда и не задумывались, сколько памяти расходует ваша программа. Даже если вы data engineer, и вам приходится работать с большим количеством данных, возможно, вы встречались с memory error. В дальнейшим мы рассмотрим сколько расходуют памяти примитивные объекты в python.
И прежде чем лезть в дебри, укажем момент работы с памятью в Python, который должен знать даже программист, который никогда не работает с крупными проектами или большими данными.
Работа в Python с памятью происходит достаточно просто и понятно, используя систему счетчиков ссылок, и память, занимавшую этим объектом, освобождается, когда счетчик равен нулю.
Рассмотрим размер основных примитивных типов данных: integer(int), float, tuple, string(str), list, dict.
Каков размер самого простого и часто используемого объекта int? Если вы перешли на Python с какого-либо С подобного языка, то ответите, что не более 8 байт. Сравним с python:
Я использую Python версии 3.7.0 64x.
В 64 битном С, int занимает 4 байта, а это гораздо меньше, чем в Python.
Числа с плавающей запятой в Python:
А что насчёт строковых значений?
Пустая строка в Python в 64 битной среде, занимает 49 байт!
Затем потребление памяти увеличивается за счет увеличения полезного размера значения.
Рассмотрим еще несколько не менее важных объектов:
В 64-битном С размер пустого std::list() равен 16 байт и это в 4 раза меньше, чем в Python.
Например, пустой словарь в С занимает 48 байт.
Ну что? Мы разобрали занимаемый объем памяти самых популярных объектов в Python. Но, наверное, никто из читателей после этого сравнения не перейдет на С, я тоже не перейду. Для того, чтобы качественно писать код, с точки зрения оптимизации, мы должны иметь представление о требованиях создаваемых нами объектов. Посмотрим, как происходит выделение памяти под капотом…
внутреннее управление памятью
Я упоминал выше, что когда счетчик ссылок обнуляется, память освобождается, звучит это просто, понятно и логично, но здесь есть несколько подводных камней.
Обратимся к истории…
Возьмём любую версию python, ниже 2.1.
Если заглянуть под капот, то мы обнаружим, что если ОС выделила Python программе память, то эта память никогда не вернется в ОС. Интерпретатор Python оставляет эту память себе, до следующего использования, это ускоряет работу программы, так как ОС не требуется постоянное выделение памяти для процесса. Но если в программе есть место, где потребление памяти резко возрастает на небольшой промежуток времени и дальше программа требует значительно меньше памяти, то Python всю память, которая требуется для этого пика будет сохранять за собой и не отдаст ее ОС, что приводит к уменьшению производительности системы в целом.
Распределитель памяти Python, называемый pymalloc, был написан Владимиром Марангозова и изначально был экспериментальной функцией Python 2.1 и 2.2, прежде чем стать включенной по умолчанию в 2.3.
В python программах используется много различных мелких объектов, которые то создаются, то уничтожаются, для этого вызываются функции malloc() (для выделения) и free() (для освобождения). Я уже упоминал, что выделение памяти ОС несколько затяжной процесс, поэтому pymalloc выделяет память куском в 256 Кб и называется это арена. Сама арена делится на пулы размер каждого 4Кб, и пулы уже делятся на блоки фиксированного размера под наши объекты.
Если мы создаем объект, распределитель проверяет, есть ли пулы, с блоками нужного нам размера. Связанный массив usedpools хранит пулы каждого размера. И каждый пул имеет связанный список доступных блоков. Если есть нужный нам блок, то мы берем его из списка пула, эта операция очень быстрая.
Если нет пулов, разбитых на нужные нам блоки, то нужно найти свободный пул, свободные пулы хранятся в связанном списке freepools. Если в списке есть пул, мы берем его, если нет, то придется выделить новый пул, если в Арене еще есть место, то мы отрежем новый пул используя arenabase. Если в Арене нет места, придется выделить новую Арену, вызвав malloc(). Теперь, когда у нас есть нужные блоки, забираем его с помощью usedpools.
Когда программа удаляет объект, освобождается блок. Блок помещается в свободный список пула и если пул был полностью выделен, то он добавляется к usedpools (список пулов, имеющих свободные блоки), а если пул теперь полностью свободен, то он добавляется к freepools.
В данной статье мы рассмотрели, как устроено распределение памяти в Python. Как вы могли заметить, Python очень прожорливый на память язык.
Основы управления памятью в Python
Вступление
Управление памятью — это процесс эффективного распределения, выделения и координации памяти, так что все процессы работали бы гладко и могли бы оптимально получать доступ к различным системным ресурсам. Управление памятью также включает в себя очистку памяти от объектов, к которым больше нет доступа.
В Python диспетчер памяти отвечает за такие задачи, периодически выполняя очистку, выделение и управление памятью. В отличие от C, Java и других языков программирования, Python управляет объектами с помощью подсчета ссылок. Это означает, что диспетчер памяти отслеживает количество ссылок на каждый объект в программе. Когда счетчик ссылок объекта падает до нуля, что означает, что объект больше не используется, сборщик мусора (часть диспетчера памяти) автоматически освобождает память от этого конкретного объекта.
Пользователю не нужно беспокоиться об управлении памятью, поскольку процесс выделения и удаления памяти полностью автоматизирован. Восстановленная память может использоваться другими объектами.
Python Garbage Collection (Уборка мусора)
Как объяснялось ранее, Python удаляет объекты, на которые больше нет ссылок в программе, чтобы освободить пространство памяти. Этот процесс, в котором Python освобождает блоки памяти, которые больше не используются, называется Garbage Collection. Python Garbage Collector (GC) работает во время выполнения программы и запускается, если счетчик ссылок уменьшается до нуля. Счетчик ссылок увеличивается, если объекту присваивается новое имя или он помещается в контейнер, такой как кортеж или словарь. Аналогично, счетчик ссылок уменьшается, когда ссылка на объект переназначается, когда ссылка на объект выходит из области видимости или когда объект удаляется.
Примеры, когда количество ссылок увеличивается:
Память представляет собой кучу, которая содержит объекты и другие структуры данных, используемые в программе. Выделение и перераспределение этого пространства кучи контролируется менеджером памяти Python с помощью функций API.
Объекты Python в памяти
Каждая переменная в Python действует как объект. Объекты могут быть простыми (содержащими числа, строки и т. д.) Или контейнерами (словарями, списками или пользовательскими классами). Кроме того, Python является динамически типизированным языком, что означает, что нам не нужно объявлять переменные или их типы перед их использованием в программе.
Если вы посмотрите на первые 2 строки вышеуказанной программы, объект x известен. Если мы удалим объект x и пытаемся его использовать, мы получим ошибку, в которой говорится, что переменная x не определена.
Вы можете видеть, что сборка мусора в Python полностью автоматизирована, и программисту не нужно беспокоиться об этом, в отличие от таких языков, как C.
Модификация Garbage Collector
У сборщика мусора Python есть три поколения (generations), по которым классифицируются объекты. Новый объект в начальной точке своего жизненного цикла — это первое поколение garbage collector. Если новый объект выживает процесс сборки мусора, то он перемещается в следующее поколение. В каждой из 3 поколений есть специальный счетчик и порог срабатывания, при достижении которых срабатывает процесс сборки мусора. Каждый счетчик хранит количество аллокаций минус количество деаллокаций в данной генерации. Чем выше поколение, тем реже оно сканируется на мусор. Так-как новые объекты зачастую имеют очень маленький срок жизни (являются временными), то имеет смысл опрашивать их чаще, чем те, которые уже прошли через несколько этапов сборки мусора.
Более ранние поколения также собирают мусор чаще, чем высшие поколения. Это связано с тем, что более новые объекты чаще отбрасываются, чем старые.
Модуль 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. Это приводит к уменьшению утечек памяти и повышению производительности.


