← Назад к вопросам

Что такое Pool в сборщике мусора (Garbage collector)?

1.7 Middle🔥 111 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Pool в сборщике мусора (Garbage collector)

Pool в контексте сборщика мусора — это набор объектов, которые находятся в одном поколении памяти. Python использует поколенческий (generational) алгоритм сборки мусора, где объекты разделены на три поколения (generation 0, 1, 2), и каждое поколение имеет свой пул объектов.

Как работает Garbage Collector в Python

Пython использует комбинацию двух механизмов:

  1. Reference counting (подсчёт ссылок) — первичный механизм
  2. Generational garbage collection — для обнаружения циклических ссылок

Reference counting

Каждый объект имеет счётчик ссылок. Когда счётчик становится 0, объект немедленно удаляется:

import sys

class MyObject:
    pass

obj = MyObject()
print(sys.getrefcount(obj))  # 2 (сама переменная + аргумент getrefcount)

obj2 = obj
print(sys.getrefcount(obj))  # 3 (ещё одна ссылка)

del obj2
print(sys.getrefcount(obj))  # 2 (ссылка удалена)

del obj  # Объект удаляется, когда счётчик = 0

Проблема: циклические ссылки

class Node:
    def __init__(self):
        self.ref = None

# Циклическая ссылка
node1 = Node()
node2 = Node()
node1.ref = node2
node2.ref = node1

# Счётчики: node1.ref_count = 2, node2.ref_count = 2
# Даже после del node1; del node2, объекты не удаляются!
# Они остаются в памяти (утечка памяти)

Поколения (Generations) в сборщике мусора

Для решения проблемы циклических ссылок Python использует generational garbage collector. Объекты делятся на три поколения:

import gc

# Посмотреть статистику поколений
print(gc.get_stats())

# Посмотреть объекты в каждом поколении
for gen in range(3):
    objects = gc.get_objects(gen)
    print(f"Generation {gen}: {len(objects)} объектов")

Generation 0 (молодое поколение):

  • Новые объекты попадают сюда
  • Сбора мусора происходит часто (быстро)
  • Мало объектов, поэтому сборка дешёвая

Generation 1 (промежуточное):

  • Объекты, пережившие несколько сборок Gen 0
  • Сборка происходит реже

Generation 2 (старое поколение):

  • Долгоживущие объекты
  • Сборка происходит редко

Pool в контексте поколений

import gc

# Установить пороги для каждого поколения
# (количество объектов, при котором запускается сборка)
gc.set_threshold(700, 10, 10)
# Gen0: 700 новых объектов → сборка
# Gen1: 10 сборок Gen0 → сборка
# Gen2: 10 сборок Gen1 → сборка

# Посмотреть текущие пороги
print(gc.get_threshold())  # (700, 10, 10)

# Статистика сборок
print(gc.get_count())  # (объектов в Gen0, в Gen1, в Gen2)

Pool и производительность

import gc
import time

def benchmark_with_gc():
    """Тест с включённым GC"""
    start = time.time()
    for i in range(1000000):
        x = [1, 2, 3]
    return time.time() - start

def benchmark_without_gc():
    """Тест с отключённым GC"""
    gc.disable()  # Отключаем сборщик
    start = time.time()
    for i in range(1000000):
        x = [1, 2, 3]
    elapsed = time.time() - start
    gc.enable()  # Включаем обратно
    return elapsed

with_gc = benchmark_with_gc()
without_gc = benchmark_without_gc()

print(f"С GC: {with_gc:.4f}s")
print(f"Без GC: {without_gc:.4f}s")
# Обычно без GC быстрее на 5-15%

Практические примеры работы с Pool

Пример 1: Отключение GC для интенсивных вычислений

import gc

def intensive_computation():
    """Функция с интенсивными вычислениями"""
    # Отключаем GC во время интенсивной работы
    gc_enabled = gc.isenabled()
    gc.disable()
    
    try:
        result = sum(i*i for i in range(10000000))
    finally:
        # Восстанавливаем состояние
        if gc_enabled:
            gc.enable()
            gc.collect()  # Принудительная сборка
    
    return result

Пример 2: Отслеживание утечек памяти

import gc
import sys

class LeakyObject:
    pass

# Создаём циклическую ссылку
objs = []
for i in range(100):
    obj = LeakyObject()
    obj.ref = obj  # Циклическая ссылка
    objs.append(obj)

print(f"До сборки: {len(gc.get_objects())} объектов")

# GC автоматически находит и удаляет циклические объекты
gc.collect()

print(f"После сборки: {len(gc.get_objects())} объектов")

# Просмотр собираемых объектов
gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect()
print(f"Собранные объекты: {len(gc.garbage)}")

Пример 3: Оптимизация памяти с pooling

from typing import List
import gc

class ObjectPool:
    """Pool объектов для переиспользования"""
    def __init__(self, object_type, initial_size=100):
        self.pool: List = [object_type() for _ in range(initial_size)]
        self.object_type = object_type
    
    def acquire(self):
        """Получить объект из pool'а"""
        if self.pool:
            return self.pool.pop()
        return self.object_type()  # Создать новый, если пул пуст
    
    def release(self, obj):
        """Вернуть объект в pool"""
        # Очистить состояние объекта
        if hasattr(obj, '__dict__'):
            obj.__dict__.clear()
        self.pool.append(obj)

# Использование
class DataObject:
    def __init__(self):
        self.data = None

pool = ObjectPool(DataObject, initial_size=1000)

# Вместо создания новых объектов
for i in range(10000):
    obj = pool.acquire()
    obj.data = f"Value {i}"
    # ... используем объект
    pool.release(obj)  # Возвращаем в пул

# Это снижает нагрузку на GC

Пример 4: Мониторинг поколений

import gc
import weakref

class Monitor:
    """Мониторинг сборки мусора"""
    def __init__(self):
        self.collections = 0
        # Устанавливаем callback
        gc.callbacks.append(self.on_collect)
    
    def on_collect(self, phase, info):
        self.collections += 1
        if self.collections % 100 == 0:
            stats = gc.get_stats()
            print(f"Сборка #{self.collections}")
            for gen, stat in enumerate(stats):
                print(f"  Gen {gen}: collections={stat.get('collections', 0)}")

monitor = Monitor()

# Создаём объекты
for i in range(100000):
    x = [1, 2, 3, 4, 5]

print(f"Всего сборок: {monitor.collections}")

Когда Pool важен

# ПРОБЛЕМА: Высокочастотный трейдинг
def high_frequency_trading():
    """Создаёт много временных объектов"""
    for tick in market_data_stream:  # Миллионы тиков в секунду
        order = Order(tick)  # Каждый раз новый объект
        # GC может срабатывать между тиками, нарушая timing

# РЕШЕНИЕ: Использовать pool
order_pool = ObjectPool(Order, 10000)

for tick in market_data_stream:
    order = order_pool.acquire()
    order.update(tick)
    # ...
    order_pool.release(order)

Заключение

Pool в сборщике мусора Python — это концепция, где объекты группируются по возрасту (поколения). Понимание того, как работают пулы и поколения GC, важно для оптимизации производительности приложений, особенно в системах с высокой нагрузкой. Использование ручного pool'инга объектов может значительно снизить нагрузку на GC и улучшить производительность приложения.