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

Spread the love

Перевод статьи: Basics of Memory Management in Python

Вступление

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

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

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

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

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

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

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

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

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

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

Рассмотрим пример:

>>> x = 5
>>> print(x)
5
>>> del x
>>> print(x)
Traceback (most reent call last):
  File "<mem_manage>", line 1, in <module>
    print(x)
NameError : name 'x' is not defined

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

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

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

У сборщика мусора Python есть три поколения (generations), по которым классифицируются объекты. Новый объект в начальной точке своего жизненного цикла – это первое поколение garbage collector. Если новый объект выживает процесс сборки мусора, то он перемещается в следующее поколение. В каждой из 3 поколений есть специальный счетчик и порог срабатывания, при достижении которых срабатывает процесс сборки мусора. Каждый счетчик хранит количество аллокаций минус количество деаллокаций в данной генерации. Чем выше поколение, тем реже оно сканируется на мусор. Так-как новые объекты зачастую имеют очень маленький срок жизни (являются временными), то имеет смысл опрашивать их чаще, чем те, которые уже прошли через несколько этапов сборки мусора. 

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

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

import gc
print(gc.get_threshold())

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

(700, 10, 10)

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

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

gc.set_threshold(900, 15, 15)

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

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

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

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

>>> def create_cycle():
...     list = [8, 9, 10]
...     list.append(list)
...     return list
... 
>>> create_cycle()
[8, 9, 10, [...]]

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

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

import gc

n = gc.collect()
print("Number of unreachable objects collected by GC:", n)

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

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

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

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

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

import sys, gc

def create_cycle():
    list = [8, 9, 10]
    list.append(list)

def main():
    print("Creating garbage...")
    for i in range(8):
        create_cycle()

    print("Collecting...")
    n = gc.collect()
    print("Number of unreachable objects collected by GC:", n)
    print("Uncollectable garbage:", gc.garbage)

if __name__ == "__main__":
    main()
    sys.exit()

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

Creating garbage...
Collecting...
Number of unreachable objects collected by GC: 8
Uncollectable garbage: []

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

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

Заключение

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

Была ли вам полезна эта статья?
[24 / 4.2]

Spread the love
Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments