Урок 09 из 12 · 3 минуты чтения

Память и производительность CPython

Подсчёт ссылок и сборщик циклов, слабые ссылки, интернирование чисел и строк, копирование, сложность операций, профилирование и способы ускорения.

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

В CPython у каждого объекта есть счётчик ссылок. Когда он падает до нуля, объект освобождается сразу — поэтому файл, на который больше нет ссылок, закрывается в тот же момент. sys.getrefcount(x) покажет счётчик (на единицу больше — сам аргумент).

Циклические ссылки — a ссылается на b, b на a — счётчик до нуля не доведут. Их находит циклический сборщик модуля gc, который работает по поколениям: молодые объекты проверяются чаще. В цикле момент вызова __del__ непредсказуем, поэтому ресурсы закрывают через with.

Слабые ссылки

Слабая ссылка не увеличивает счётчик: объект удаляется, когда исчезнут обычные ссылки, а слабая начинает возвращать None.

import weakref
cache = weakref.WeakValueDictionary()

Применения — кэши и реестры, которые не должны продлевать жизнь объектов, разрыв циклов «родитель ↔ ребёнок», наблюдатели. weakref.finalize — надёжная замена __del__. Не у всех типов есть слабые ссылки: у int, str, tuple, list их нет.

Почему a is b иногда True

CPython заранее создаёт целые от −5 до 256 и переиспользует их. Строки, похожие на идентификаторы, и одинаковые константы в одном блоке кода интернируются. Поэтому a = 256; b = 256; a is b — True, а с 257 результат зависит от того, как выполнялся код. Это детали реализации: значения сравнивают через ==.

Копирование

  • b = a — не копия, а второе имя.
  • Поверхностная копия — новый контейнер со старыми элементами: copy.copy(x), x[:], list(x), x.copy().
  • Глубокая — копирует всё рекурсивно: copy.deepcopy(x).
grid = [[0] * 3] * 3      # три ссылки на один и тот же список
grid[0][0] = 1            # меняется «каждая» строка
grid = [[0] * 3 for _ in range(3)]   # правильно

Сложность операций

Операцияlistdict, set
доступ по индексу или ключуO(1)O(1) в среднем
append, addO(1) амортизированноO(1)
вставка в началоO(n)—
x in ...O(n)O(1)

x in list внутри цикла — самая частая причина неожиданного O(n²). Очередь с двух концов — deque, сортировка — O(n log n).

Сначала измерить

  • cProfile (и snakeviz для картинки) — время по функциям локально.
  • py-spy — сэмплирующий профилировщик, подключается к работающему процессу без перезапуска; py-spy dump показывает стеки зависшего процесса.
  • line_profiler — построчно; tracemalloc, memray — память.
  • timeit — для микроизмерений.

Профилируют под реалистичной нагрузкой: узким местом часто оказывается ввод-вывод, а не процессор.

Почему Python медленный и что делать

Каждая операция проверяет типы, числа — объекты в куче. Ускоряют так:

  • алгоритм и правильные структуры данных — обычно самый большой выигрыш;
  • встроенные функции на C вместо ручных циклов: sum, sorted, str.join, множества;
  • векторизация — NumPy и pandas выполняют циклы на C;
  • расширения на Cython или Rust, Numba для числовых функций, PyPy;
  • процессы — для параллельных вычислений.

Python 3.11 стал быстрее на 10–60 % за счёт адаптивного интерпретатора.

Что запомнить

  • Объект освобождается, когда счётчик ссылок падает до нуля; циклы ловит gc.
  • Слабые ссылки — для кэшей, которые не держат объекты.
  • is для чисел и строк — деталь реализации.
  • in по списку в цикле — O(n²).
  • Сначала профиль, потом оптимизация.

Проверка урока

Войдите, чтобы проверить себя: сданные проверки всех уроков курса отмечают его темы пройденными на карте и в ИПР.

Вопросы по теме

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

Партнёр платформы

Разбор собеседования с sotto

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

Разбор собеседования