Автор оригинала: Doug Hellmann.
Цель:
Управляет памятью, используемой объектами Python
gc
раскрывает базовый механизм управления памятью Python, автоматический сборщик мусора. Модуль включает функции для управления работой сборщика и проверки объектов, известных системе, ожидающих сбора или застрявших в циклах обращения и не подлежащих освобождению.
Ссылки на трассировку
С помощью gc
входящие и исходящие ссылки между объектами могут использоваться для поиска циклов в сложных структурах данных. Если известно, что структура данных имеет цикл, для проверки ее свойств можно использовать специальный код. Если цикл находится в неизвестном коде, функции get_referents ()
и get_referrers ()
можно использовать для создания общих инструментов отладки.
Например, get_referents ()
показывает объекты, на которые ссылаются входные аргументы.
gc_get_referents.py
import gc import pprint class Graph: def __init__(self, name): self.name name self.next None def set_next(self, next): print('Linking nodes {}.next = {}'.format(self, next)) self.next next def __repr__(self): return '{}({})'.format( self.__class__.__name__, self.name) # Construct a graph cycle one Graph('one') two Graph('two') three Graph('three') one.set_next(two) two.set_next(three) three.set_next(one) print() print('three refers to:') for r in gc.get_referents(three): pprint.pprint(r)
В этом случае экземпляр Graph
three
содержит ссылки на свой словарь экземпляра (в атрибуте __dict__
) и его класс.
$ python3 gc_get_referents.py Linking nodes Graph(one).next = Graph(two) Linking nodes Graph(two).next = Graph(three) Linking nodes Graph(three).next = Graph(one) three refers to: {'name': 'three', 'next': Graph(one)}
В следующем примере используется Queue
для выполнения обхода в ширину всех ссылок на объекты в поисках циклов. Элементы, вставленные в очередь, представляют собой кортежи, содержащие цепочку ссылок на данный момент и следующий объект для проверки. Он начинается с три
и смотрит на все, к чему относится. Пропуск классов позволяет не смотреть на методы, модули и т. Д.
gc_get_referents_cycles.py
import gc import pprint import queue class Graph: def __init__(self, name): self.name name self.next None def set_next(self, next): print('Linking nodes {}.next = {}'.format(self, next)) self.next next def __repr__(self): return '{}({})'.format( self.__class__.__name__, self.name) # Construct a graph cycle one Graph('one') two Graph('two') three Graph('three') one.set_next(two) two.set_next(three) three.set_next(one) print() seen set() to_process queue.Queue() # Start with an empty object chain and Graph three. to_process.put(([], three)) # Look for cycles, building the object chain for each object # found in the queue so the full cycle can be printed at the # end. while not to_process.empty(): chain, next to_process.get() chain chain[:] chain.append(next) print('Examining:', repr(next)) seen.add(id(next)) for r in gc.get_referents(next): if isinstance(r, str) or isinstance(r, type): # Ignore strings and classes pass elif id(r) in seen: print() print('Found a cycle to {}:'.format(r)) for i, link in enumerate(chain): print(' {}: '.format(i), end' ') pprint.pprint(link) else: to_process.put((chain, r))
Цикл в узлах легко найти, наблюдая за объектами, которые уже были обработаны. Чтобы не хранить ссылки на эти объекты, их значения id ()
кэшируются в наборе. Объекты словаря, найденные в цикле, являются значениями __dict__
для экземпляров Graph
и содержат их атрибуты экземпляров.
$ python3 gc_get_referents_cycles.py Linking nodes Graph(one).next = Graph(two) Linking nodes Graph(two).next = Graph(three) Linking nodes Graph(three).next = Graph(one) Examining: Graph(three) Examining: {'name': 'three', 'next': Graph(one)} Examining: Graph(one) Examining: {'name': 'one', 'next': Graph(two)} Examining: Graph(two) Examining: {'name': 'two', 'next': Graph(three)} Found a cycle to Graph(three): 0: Graph(three) 1: {'name': 'three', 'next': Graph(one)} 2: Graph(one) 3: {'name': 'one', 'next': Graph(two)} 4: Graph(two) 5: {'name': 'two', 'next': Graph(three)}
Принудительный сбор мусора
Хотя сборщик мусора запускается автоматически, когда интерпретатор выполняет программу, его можно запустить в определенное время, когда нужно освободить много объектов или не выполняется много работы, и сборщик не повлияет на производительность приложения. Запуск сбора с помощью collect ()
.
gc_collect.py
import gc import pprint class Graph: def __init__(self, name): self.name name self.next None def set_next(self, next): print('Linking nodes {}.next = {}'.format(self, next)) self.next next def __repr__(self): return '{}({})'.format( self.__class__.__name__, self.name) # Construct a graph cycle one Graph('one') two Graph('two') three Graph('three') one.set_next(two) two.set_next(three) three.set_next(one) # Remove references to the graph nodes in this module's namespace one two three None # Show the effect of garbage collection for i in range(2): print('\nCollecting {} ...'.format(i)) n gc.collect() print('Unreachable objects:', n) print('Remaining Garbage:', end' ') pprint.pprint(gc.garbage)
В этом примере цикл очищается, как только сбор выполняется в первый раз, поскольку ничто не относится к узлам Graph
, кроме них самих. collect ()
возвращает количество найденных «недостижимых» объектов. В данном случае значение равно 6
, потому что есть три объекта с их словарями атрибутов экземпляров.
$ python3 gc_collect.py Linking nodes Graph(one).next = Graph(two) Linking nodes Graph(two).next = Graph(three) Linking nodes Graph(three).next = Graph(one) Collecting 0 ... Unreachable objects: 6 Remaining Garbage: [] Collecting 1 ... Unreachable objects: 0 Remaining Garbage: []
Поиск ссылок на объекты, которые невозможно собрать
Искать объект, содержащий ссылку на другой объект, немного сложнее, чем увидеть, на что ссылается объект. Поскольку код, запрашивающий ссылку, сам должен содержать ссылку, некоторые источники перехода необходимо игнорировать. В этом примере создается цикл графа, затем он обрабатывает экземпляры Graph
и удаляет ссылку в «родительском» узле.
gc_get_referrers.py
import gc import pprint class Graph: def __init__(self, name): self.name name self.next None def set_next(self, next): print('Linking nodes {}.next = {}'.format(self, next)) self.next next def __repr__(self): return '{}({})'.format( self.__class__.__name__, self.name) def __del__(self): print('{}.__del__()'.format(self)) # Construct a graph cycle one Graph('one') two Graph('two') three Graph('three') one.set_next(two) two.set_next(three) three.set_next(one) # Collecting now keeps the objects as uncollectable, # but not garbage. print() print('Collecting...') n gc.collect() print('Unreachable objects:', n) print('Remaining Garbage:', end' ') pprint.pprint(gc.garbage) # Ignore references from local variables in this module, global # variables, and from the garbage collector's bookkeeping. REFERRERS_TO_IGNORE [locals(), globals(), gc.garbage] def find_referring_graphs(obj): print('Looking for references to {!r}'.format(obj)) referrers (r for r in gc.get_referrers(obj) if r not in REFERRERS_TO_IGNORE) for ref in referrers: if isinstance(ref, Graph): # A graph node yield ref elif isinstance(ref, dict): # An instance or other namespace dictionary for parent in find_referring_graphs(ref): yield parent # Look for objects that refer to the objects in the graph. print() print('Clearing referrers:') for obj in [one, two, three]: for ref in find_referring_graphs(obj): print('Found referrer:', ref) ref.set_next(None) del ref # remove reference so the node can be deleted del obj # remove reference so the node can be deleted # Clear references held by gc.garbage print() print('Clearing gc.garbage:') del gc.garbage[:] # Everything should have been freed this time print() print('Collecting...') n gc.collect() print('Unreachable objects:', n) print('Remaining Garbage:', end' ') pprint.pprint(gc.garbage)
Такая логика является излишней, если циклы понятны, но для необъяснимого цикла в данных использование get_referrers ()
может выявить неожиданную взаимосвязь.
$ python3 gc_get_referrers.py Linking nodes Graph(one).next = Graph(two) Linking nodes Graph(two).next = Graph(three) Linking nodes Graph(three).next = Graph(one) Collecting... Unreachable objects: 0 Remaining Garbage: [] Clearing referrers: Looking for references to Graph(one) Looking for references to {'name': 'three', 'next': Graph(one)} Found referrer: Graph(three) Linking nodes Graph(three).next = None Looking for references to Graph(two) Looking for references to {'name': 'one', 'next': Graph(two)} Found referrer: Graph(one) Linking nodes Graph(one).next = None Looking for references to Graph(three) Looking for references to {'name': 'two', 'next': Graph(three)} Found referrer: Graph(two) Linking nodes Graph(two).next = None Clearing gc.garbage: Collecting... Unreachable objects: 0 Remaining Garbage: [] Graph(one).__del__() Graph(two).__del__() Graph(three).__del__()
Пороги сбора и поколения
Сборщик мусора поддерживает три списка объектов, которые он видит при запуске, по одному для каждого «поколения», отслеживаемого сборщиком. По мере того, как объекты исследуются в каждом поколении, они либо собираются, либо стареют в последующих поколениях, пока, наконец, не достигнут стадии, на которой они хранятся постоянно.
Подпрограммы сборщика могут быть настроены на разную частоту в зависимости от разницы между количеством выделенных и освобожденных объектов между запусками. Когда количество выделений минус количество освобожденных превышает пороговое значение для генерации, запускается сборщик мусора. Текущие пороги можно проверить с помощью get_threshold ()
.
gc_get_threshold.py
import gc print(gc.get_threshold())
Возвращаемое значение – кортеж с порогом для каждого поколения.
$ python3 gc_get_threshold.py (700, 10, 10)
Пороги можно изменить с помощью set_threshold ()
. В этом примере программы используется аргумент командной строки для установки порога генерации 0
, а затем выделяется серия объектов.
gc_threshold.py
import gc import pprint import sys try: threshold int(sys.argv[1]) except (IndexError, ValueError, TypeError): print('Missing or invalid threshold, using default') threshold 5 class MyObj: def __init__(self, name): self.name name print('Created', self.name) gc.set_debug(gc.DEBUG_STATS) gc.set_threshold(threshold, 1, 1) print('Thresholds:', gc.get_threshold()) print('Clear the collector by forcing a run') gc.collect() print() print('Creating objects') objs [] for i in range(10): objs.append(MyObj(i)) print('Exiting') # Turn off debugging gc.set_debug(0)
Различные пороговые значения вводят очистку сборки мусора в разное время, показанное здесь, потому что отладка включена.
$ python3 -u gc_threshold.py 5 Thresholds: (5, 1, 1) Clear the collector by forcing a run gc: collecting generation 2... gc: objects in each generation: 575 1279 4527 gc: objects in permanent generation: 0 gc: done, 0.0009s elapsed Creating objects gc: collecting generation 0... gc: objects in each generation: 3 0 6137 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 0 Created 1 Created 2 gc: collecting generation 0... gc: objects in each generation: 3 2 6137 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 3 Created 4 Created 5 gc: collecting generation 1... gc: objects in each generation: 4 4 6137 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 6 Created 7 Created 8 gc: collecting generation 0... gc: objects in each generation: 4 0 6144 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 9 Exiting
Меньший порог приводит к более частому запуску разверток.
$ python3 -u gc_threshold.py 2 Thresholds: (2, 1, 1) Clear the collector by forcing a run gc: collecting generation 2... gc: objects in each generation: 575 1279 4527 gc: objects in permanent generation: 0 gc: done, 0.0026s elapsed gc: collecting generation 0... gc: objects in each generation: 1 0 6137 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Creating objects gc: collecting generation 0... gc: objects in each generation: 3 0 6137 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 0 gc: collecting generation 1... gc: objects in each generation: 2 2 6137 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 1 Created 2 gc: collecting generation 0... gc: objects in each generation: 2 0 6140 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 3 gc: collecting generation 0... gc: objects in each generation: 3 1 6140 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 4 Created 5 gc: collecting generation 1... gc: objects in each generation: 2 3 6140 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 6 gc: collecting generation 0... gc: objects in each generation: 3 0 6144 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 7 Created 8 gc: collecting generation 0... gc: objects in each generation: 2 2 6144 gc: objects in permanent generation: 0 gc: done, 0.0000s elapsed Created 9 Exiting
Отладка
Отладка утечек памяти может быть сложной задачей. gc
включает несколько параметров, позволяющих раскрыть внутреннюю работу и облегчить работу. Параметры представляют собой битовые флаги, предназначенные для объединения и передачи в set_debug ()
для настройки сборщика мусора во время работы программы. Информация об отладке печатается в sys.stderr
.
Флаг DEBUG_STATS
включает статистические отчеты, заставляя сборщик мусора сообщать, когда он запущен, количество отслеживаемых объектов для каждого поколения и количество времени, которое потребовалось для выполнения очистки.
gc_debug_stats.py
import gc gc.set_debug(gc.DEBUG_STATS) gc.collect() print('Exiting')
В этом примере выходных данных показаны два отдельных запуска сборщика, потому что он запускается один раз при явном вызове и второй раз при выходе из интерпретатора.
$ python3 gc_debug_stats.py gc: collecting generation 2... gc: objects in each generation: 826 471 4529 gc: objects in permanent generation: 0 gc: done, 24 unreachable, 0 uncollectable, 0.0007s elapsed Exiting gc: collecting generation 2... gc: objects in each generation: 1 0 5552 gc: objects in permanent generation: 0 gc: done, 0.0005s elapsed gc: collecting generation 2... gc: objects in each generation: 107 0 5382 gc: objects in permanent generation: 0 gc: done, 1406 unreachable, 0 uncollectable, 0.0008s elapsed gc: collecting generation 2... gc: objects in each generation: 0 0 3307 gc: objects in permanent generation: 0 gc: done, 151 unreachable, 0 uncollectable, 0.0002s elapsed
Включение DEBUG_COLLECTABLE
и DEBUG_UNCOLLECTABLE
заставляет сборщик сообщать о том, можно или нельзя собрать каждый исследуемый им объект. Если просмотра объектов, которые не могут быть собраны, недостаточно для понимания того, где хранятся данные, включите DEBUG_SAVEALL
, чтобы gc
сохранял все найденные объекты без каких-либо ссылок в Список мусора
.
gc_debug_saveall.py
import gc flags (gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL ) gc.set_debug(flags) class Graph: def __init__(self, name): self.name name self.next None def set_next(self, next): self.next next def __repr__(self): return '{}({})'.format( self.__class__.__name__, self.name) class CleanupGraph(Graph): def __del__(self): print('{}.__del__()'.format(self)) # Construct a graph cycle one Graph('one') two Graph('two') one.set_next(two) two.set_next(one) # Construct another node that stands on its own three CleanupGraph('three') # Construct a graph cycle with a finalizer four CleanupGraph('four') five CleanupGraph('five') four.set_next(five) five.set_next(four) # Remove references to the graph nodes in this module's namespace one two three four five None # Force a sweep print('Collecting') gc.collect() print('Done') # Report on what was left for o in gc.garbage: if isinstance(o, Graph): print('Retained: {} 0x{:x}'.format(o, id(o))) # Reset the debug flags before exiting to avoid dumping a lot # of extra information and making the example output more # confusing. gc.set_debug(0)
Это позволяет проверять объекты после сборки мусора, что полезно, если, например, конструктор не может быть изменен для печати идентификатора объекта при создании каждого объекта.
$ python3 -u gc_debug_saveall.py CleanupGraph(three).__del__() Collecting gc: collectablegc: collectable gc: collectable | gc: collectable gc: collectable gc: collectable gc: collectable | gc: collectable gc: collectable | gc: collectable | gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable CleanupGraph(four).__del__() CleanupGraph(five).__del__() Done Retained: Graph(one) 0x109771828 Retained: Graph(two) 0x10979f320 Retained: CleanupGraph(four) 0x1097af4e0 Retained: CleanupGraph(five) 0x10983eb70
Для простоты DEBUG_LEAK
определяется как комбинация всех других параметров.
gc_debug_leak.py
import gc flags gc.DEBUG_LEAK gc.set_debug(flags) class Graph: def __init__(self, name): self.name name self.next None def set_next(self, next): self.next next def __repr__(self): return '{}({})'.format( self.__class__.__name__, self.name) class CleanupGraph(Graph): def __del__(self): print('{}.__del__()'.format(self)) # Construct a graph cycle one Graph('one') two Graph('two') one.set_next(two) two.set_next(one) # Construct another node that stands on its own three CleanupGraph('three') # Construct a graph cycle with a finalizer four CleanupGraph('four') five CleanupGraph('five') four.set_next(five) five.set_next(four) # Remove references to the graph nodes in this module's namespace one two three four five None # Force a sweep print('Collecting') gc.collect() print('Done') # Report on what was left for o in gc.garbage: if isinstance(o, Graph): print('Retained: {} 0x{:x}'.format(o, id(o))) # Reset the debug flags before exiting to avoid dumping a lot # of extra information and making the example output more # confusing. gc.set_debug(0)
Имейте в виду, что, поскольку DEBUG_SAVEALL
включен с помощью DEBUG_LEAK
, даже те объекты, на которые нет ссылок, которые обычно были бы собраны и удалены, сохраняются.
$ python3 -u gc_debug_leak.py CleanupGraph(three).__del__() Collecting gc: collectablegc: collectable gc: collectable | gc: collectable gc: collectable gc: collectable gc: collectable | gc: collectable gc: collectable | gc: collectable | gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable gc: collectable CleanupGraph(four).__del__() CleanupGraph(five).__del__() Done Retained: Graph(one) 0x10be10828 Retained: Graph(two) 0x10be3e320 Retained: CleanupGraph(four) 0x10be4e4e0 Retained: CleanupGraph(five) 0x10beddb70
Смотрите также
- стандартная библиотека документации для gc
- Заметки о переносе Python 2 на 3 для gc
- weakref – модуль
weakref
предоставляет способ создания ссылок на объекты без увеличения их счетчика ссылок, чтобы они по-прежнему могли собираться сборщиком мусора. - Поддержка циклической сборки мусора – справочные материалы из документации Python C API.
- Как Python управляет памятью? – статья Фредрика Лунда об управлении памятью Python.