Урок по треку Python 7 мин

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

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

На senior-собесе спрашивают, как Python управляет памятью и как вы ищете узкие места. Ответ «Python медленный» не принимается: нужно знать, где именно он медленный и что с этим делать.

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

У каждого объекта в CPython есть счётчик ссылок. Присваивание, добавление в список, передача аргументом увеличивают его, выход из области и del уменьшают. Когда счётчик падает до нуля, объект освобождается сразу. Поэтому файлы, открытые без with, в CPython обычно закрываются быстро, но полагаться на это нельзя: в PyPy и других реализациях сборка отложенная.

sys.getrefcount(x) показывает счётчик (на 1 больше из-за аргумента самой функции).

Сборщик циклов

Подсчёт ссылок не справляется с циклами: a.other = b; b.other = a. Для этого есть модуль gc: сборщик с поколениями периодически ищет недостижимые группы объектов-контейнеров и удаляет их. Его можно отключить (gc.disable()) или заморозить старые объекты (gc.freeze()), так иногда делают в веб-воркерах после форка, чтобы не портить copy-on-write страницы.

weakref создаёт ссылку, которая не увеличивает счётчик: кеши (WeakValueDictionary), обратные ссылки на родителя, подписчики.

Утечки памяти в Python

Сборщик есть, а утечки бывают:

  • Растущие глобальные структуры: кеш без ограничения, список «на всякий случай».
  • lru_cache без maxsize или на методе (держит self).
  • Обработчики и подписки, которые не удаляются.
  • Ссылки на большие объекты из traceback (sys.exc_info в долгоживущей переменной).
  • Фрагментация: процесс освободил объекты, но память ОС не вернул (аллокатор держит арены).

Инструменты: tracemalloc (снимки и сравнение, где выделялось), objgraph, memray, gc.get_objects().

Сколько весит объект

Python-объекты тяжёлые: даже маленький int занимает десятки байт, у каждого объекта заголовок со счётчиком и указателем на тип. Список хранит указатели на объекты, а не сами числа. Отсюда приёмы экономии:

  • __slots__ в классах с миллионами экземпляров.
  • array.array и numpy для однородных чисел: значения лежат подряд без объектов.
  • Генераторы вместо списков для потоковой обработки.
  • Кортежи вместо списков для неизменяемых записей.

sys.getsizeof показывает только сам объект, без того, на что он ссылается.

Как искать узкие места

  1. Измерить, а не гадать. cProfile (python -m cProfile -s cumtime app.py) показывает, где тратится время по функциям. py-spy снимает профиль работающего процесса без остановки. timeit для микробенчмарков.
  2. Найти алгоритмическую проблему. Чаще всего тормозит x in list в цикле, повторные запросы к БД в цикле (N+1), конкатенация строк в цикле, лишние копии.
  3. Только потом микрооптимизации.

Что ускоряет Python-код

  • Правильная структура данных: set и dict для поиска, deque для очереди, heapq для топ-k.
  • Встроенные функции и стандартная библиотека написаны на C: sum, sorted, max, str.join, collections.Counter, itertools.
  • ''.join(parts) вместо s += part в цикле.
  • Локальные переменные быстрее глобальных; в горячем цикле можно сохранить метод в локальное имя.
  • Векторизация через numpy, перенос горячего кода в Cython, Rust (PyO3) или C.
  • Кеширование результатов (functools.cache).
  • Новые версии CPython: 3.11 дал заметный прирост скорости за счёт специализирующего интерпретатора, в 3.13 появился экспериментальный JIT.

Частые ошибки

  • Оптимизация без профилирования.
  • Ожидание, что del x освободит память, когда на объект есть другие ссылки.
  • Чтение огромного файла целиком через read() вместо построчного обхода.
  • Кеш без ограничения размера в долгоживущем процессе.

Что спросят на собесе

  • Как Python управляет памятью: подсчёт ссылок и сборщик циклов.
  • Откуда берутся утечки памяти в Python и как их искать.
  • Как найти, почему код медленный: какими инструментами.
  • Какие приёмы ускорения вы применяли.
  • Что дают __slots__, генераторы, weakref.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (28)