Найти в Дзене

Пару слов о профилировании памяти в Python

Статья подготовлена для студентов курса «Web-разработчик на Python» в образовательном проекте OTUS. Проблемы с памятью в приложениях — явление довольно частое. Правда, в Python, где работать с памятью напрямую приходится разве что при написании CPython-расширений, сталкиваться с этим приходится реже. Ещё часть рисков снимают фреймворки. Тем не менее понимать, как распределяется память в приложении, всегда полезно. Давайте посмотрим, какие возможности у нас есть на примере небольшого Django-проекта. Итак, у нас есть простая модель данных: И десяток вьюх, отдающих JSON. И вот где-то среди них у нас утекает память. Прежде всего, нам надо сузить границы поиска. Один из способов — использовать приложение memory-profiler. В его состав входит удобный декоратор, который позволяет смотреть динамику памяти в отдельных функциях. Повесим его на одну из вьюх: Без дополнительных настроек profile выводит результаты прямо в стандартный вывод. И выглядит он примерно так: Абсолютные значения тут
Статья подготовлена для студентов курса «Web-разработчик на Python» в образовательном проекте OTUS.

Проблемы с памятью в приложениях — явление довольно частое. Правда, в Python, где работать с памятью напрямую приходится разве что при написании CPython-расширений, сталкиваться с этим приходится реже. Ещё часть рисков снимают фреймворки.

Тем не менее понимать, как распределяется память в приложении, всегда полезно. Давайте посмотрим, какие возможности у нас есть на примере небольшого Django-проекта.

Итак, у нас есть простая модель данных:

-2

И десяток вьюх, отдающих JSON. И вот где-то среди них у нас утекает память.

Прежде всего, нам надо сузить границы поиска. Один из способов — использовать приложение memory-profiler.

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

-3

Без дополнительных настроек profile выводит результаты прямо в стандартный вывод. И выглядит он примерно так:

-4

Абсолютные значения тут не настолько важны, как прирост памяти. Увы, он не всегда нагляден. Например, наполнение списка data-словарями в цикле даёт прирост в 2.5 мегабайта, хотя сумма инкрементов показывает всего 1.1. Кроме того, далеко не всегда (ладно, почти никогда) показатели памяти в рамках одного вызова расскажут об утечке — нужно собирать статистику. И для этого в memory-profiler есть разные инструменты. Но, допустим, мы нашли злополучную точку прироста памяти. На что же она уходит? Memory-profiler не показывает деталей, и тут на помощь нам приходит другая библиотека — pympler.

Она позволяет нам получить статистику разных типов данных. Давайте посмотрим на примере:

-5

Давайте посмотрим, сколько всего появилось в памяти при получении из базы 10050 записей Lesson:

-6

Итак, больше всего у нас строк. Это ожидаемо. В конце концов, в вебе почти все данные — строки. А вот на втором месте у нас словари. И их количество прямо пропорционально количеству записей, которые мы загружаем — ведь на каждый инстанс у нас создается __dict__. Кроме того, видно, что на каждый инстанс появляется еще и по одному django.db.models.base.ModelState (класс, в котором хранится состояние инстанса) и datetime.datetime — но это из-за наличия DateTimeField в модели.

Любопытно, что если мы достанем данные из базы не только для Lesson, но и для связанных таблиц, используя select_related, картина расхода памяти поменяется:

-7

Фатально увеличилось количество словарей, что логично — ведь и экземпляров различных классов теперь в разы больше. По сути, на каждый инстанс наших моделей приходится 2 словаря — для самого инстанса и для ModelState.

В целом, поведение памяти в примере вполне штатно. Я лишь хотел проиллюстрировать некоторые из существующих инструментов для профилирования памяти в python-приложениях.

Напоследок, вам «небольшая» картинка объектного графа одного инстанса Lesson, сгенерированная с помощью библиотеки objgraph =)

-8

Есть вопрос? Напишите в комментариях!

День открытых дверей курса «Web-разработчик на Python» пройдет 23 июля в 20:00!
Приглашаются все желающие!
ЗАПИСАТЬСЯ