time

Профайлинг Python/Django/Flask приложений

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

Иногда так случается, что функционал содержащий несколько строчек кода в начале процесса разработки может обрасти дополнительными возможностями и разными вызовами вспомогательных функций. Казалось бы, все хорошо, функционал реализован на все 110% но вдруг замечается легкая задумчивость при обращении к новому функционалу. Специально для таких случаев существует профайлер, который умеет подсчитывать время, проведенное исполняющим процессом внутри определенной функции и количество вызовов этой функции.

Последние версии python поставляются с тремя разными профайлерами: cProfile (рекомендуемый в большинстве случаев), profile (удобен в случае добавления функционала к стандартному профайлеру), hotshot (на данный момент не поддерживается, но обладающий двумя методами которые до сих пор актуальны - start и stop).

В качестве примера работы профайлера будет использоваться синтетический тест работы процессора - pystones

# файл tst.py

import test.pystone
test.pystone.pystones()

Ниже приведен пример запуска py-файла, рабочий выхлоп профайлера отсортирован по суммарному времени проведенному внутри определенной функции, другие возможные варианты сортировки есть тут.

python -m cProfile -s cumulative tst.py

Результат работы профайлера можно сохранить в файл

python -m cProfile -o profile.pstats tst.py

Сохраненный файл можно проанализировать с помощью сторонних утилит (kcachegrind, RunSnakeRun), либо вручную

import pstats
p = pstats.Stats('profile.pstats')

# вывести только функции, которые вызывали функцию Proc0
p.print_callers('Proc0')

Для построчного профайлинга есть модуль line_profiler, который умеет подсчитывать количество обращений к отдельным строкам кода.

# установка
pip install line_profiler

# запуск профайлера
kernprof.py tst.py

# просмотр собранных данных
python -m pstats tst.py.prof
tst.py.prof% stats

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

Кроме kcachegrind есть еще один способ визуализации - с помощью Gprof2Dot, который умеет строить дерево из вызываемых функций и визуально выделять наиболее страстные функции к CPU. Установка под Ubuntu:

sudo apt-get install graphviz
pip install gprof2dot

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

gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png

Django

Для профайлинга django-приложений есть несколько способов.

С помощью middleware, когда при каждом запросе страницы мониторятся вызовы всех функций внутри запрашиваемого view. Вариант который работает не только для обычного запроса, но и для AJAX. Еще есть модуль django-profiling, который срабатывает при наличии ?prof в запрашиваемом адресе.

С помощью декоратора для вызываемой функции/view. Вариант который сохранят каждое обращение под указанным именем + дата и время.

С помощью django-debug-toolbar. Сам по себе django-debug-toolbar не показывает данные с профайлингом, но тут описан способ как добавить вкладку Profiling.

С помощью django-extensions в который встроена возможность запуска тестового сервера с активированной возможностью профайлинга, результат сохраняется для каждого запроса.

Для визуального просмотра собранных данных можно воспользоваться описанными выше kcachegrind, gprof2dot.

Flask

Для профайлинга flask-приложений есть несколько способов.

С помощью встроенного в werkzeug профайлера.

С помощью flask-debugtoolbar, портированого с django-debug-toolbar.

Дополнительно чтиво по оптимизации

Дополнительно чтиво по измерению потребления памяти

Измерение производительности реализаций python

Небольшой бонус :), протестировать работу разных версий питона и окружений можно с помощью встроенного синтетического теста

# версия 2.7
python2.7 -c "from test import pystone;pystone.main()"

# версия 3.0
python3.0 -c "from test import pystone;pystone.main()"

# pypy
pypy -c "from test import pystone;pystone.main()""

Дополнительный материал

blog comments powered by Disqus