Профилирование кода это попытка найти узкие места в вашем коде. Профилирование может найти самые долго выполняющиеся части вашего кода. Найдя их, вы можете оптимизировать эти части удобным вам способом. Python содержит три встроенных профайлера: cProfile, profile и hotshot. В соответствии с документацией Python, hotshot «не поддерживается, и может быть удален из Python». Модуль profile это в корне своем модуль Python, но добавляет много чего сверху в профилированные программы. Поэтому мы сфокусируемся на cProfile, который содержит интерфейс, который имитирует модуль profile.
Профилирование кода при помощи cProfile
Профилирование кода с cProfile это достаточно просто. Все что вам нужно сделать, это импортировать модуль и вызвать его функцию run. Давайте посмотрим на простой пример:
1 2 3 4 |
import hashlib import cProfile cProfile.run("hashlib.md5(b'abcdefghijkl').digest()") |
Здесь мы импортировали модуль hashlib и использовали cProfile для профилирования того, что создал хеш MD5. Первая строка показывает, что в ней 4 вызова функций. Следующая строка говорит нам, в каком порядке результаты выдачи. Здесь есть несколько столбцов.
- ncalls – это количество совершенных вызовов;
- tottime – это все время, потраченное в данной функции;
- percall – ссылается на коэффициент tottime, деленный на ncalls;
- cumtime – совокупное время, потраченное как в данной функции, так и наследуемых функциях. Это работает также и с рекурсивными функциями!
- Второй столбец percall – это коэффициент cumtime деленный на примитивные вызовы;
- filename:lineno(function) предоставляет соответствующие данные о каждой функции.
Примитивный вызов – это вызов, который не был совершен при помощи рекурсии. Это очень интересный пример, так как здесь нет очевидных узких мест. Давайте создадим часть кода с узкими местами, и посмотрим, обнаружит ли их профайлер.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# -*- coding: utf-8 -*- import time def fast(): print("Я быстрая функция") def slow(): time.sleep(3) print("Я очень медленная функция") def medium(): time.sleep(0.5) print("Я средняя функция...") def main(): fast() slow() medium() if __name__ == '__main__': main() |
Сохраняем программу как ptest.py. В этом примере мы создали четыре функции. Первые три работают с разными темпами. Быстрая функция запустится с нормальной скоростью, средняя функция потратит примерно полсекунды на запуск, медленная функция потратит примерно три секунды для запуска. Главная функция вызывает остальные три. Давайте запустим cProfile в этой маленькой глупой программе:
1 2 3 4 |
import cProfile import ptest cProfile.run('ptest.main()') |
На этот раз мы видим, что у программы ушло 3.5 секунды на запуск. Если вы изучите результаты, то увидите, что cProfile выявил медленную функцию, которая тратит 3 секунды на запуск. Это и есть самая «слабая» часть основной функции. Обычно, когда вы обнаруживаете такие места, вы можете попытаться найти самый быстрый способ выполнения вашего кода, или прийти к выводу, что такая задержка приемлема. В этом примере, мы знаем, что лучший способ ускорить функцию, то убрать вызов time.sleep, или, по крайней мере, снизить продолжительность сна. Вы можете также вызвать cProfile в командной строке, вместо применения в интерпретаторе. Как это сделать:
1 |
python -m cProfile ptest.py |
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Таким образом, будет запущен cProfile в вашем скрипте по аналогии с тем, как мы делали это ранее. Но что если вам нужно сохранить выдачу профайлера? Что-ж, это очень просто с cProfile! Все что вам нужно, это передать ему команду –o, за которой следует название (или путь) файла выдачи. Вот пример:
1 |
python -m cProfile -o output.txt ptest.py |
К сожалению, выдаваемый файл едва ли можно назвать читаемым. Если вы хотите прочесть файл, тогда вам нужно использовать модуль Python pstats. Вы можете использовать pstats для форматирования выдачи разными способами. Вот небольшой код, который показывает, как получить выдачу, по аналогии с тем, как мы делали это раньше:
1 2 3 4 |
import pstats p = pstats.Stats("output.txt") p.strip_dirs().sort_stats(-1).print_stats() |
Вызов strip_dirs вырезает все пути к модулям из вывода, пока вызов sort_stats делает сортировку, которая нужна нам для виденья картины. Существует множества очень интересных примеров в документации cProfile, которые наглядно демонстрируют различные пути извлечения информации с использованием модуля pstats.
Выясняем скорость загрузки сайтов
Давайте немного развлечемся. Сделаем небольшой марафон и узнаем какой сайт быстрее всех откроется из нашей программы. Узнать больше насчет производительности requests можно узнать в данной статье.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# -*- coding: utf-8 -*- import requests import cProfile def facebook(): requests.get('https://facebook.com') def google(): requests.get('https://google.com') def twitter(): requests.get('https://twitter.com') def vk(): requests.get('https://vk.com') def main(): facebook() google() twitter() vk() cProfile.run('main()') |
Результат
1 2 3 4 5 6 7 8 9 10 11 |
python3 site_speed_test.py 61356 function calls (61000 primitive calls) in 2.683 seconds ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.743 0.743 ptest.py:10(google) 1 0.001 0.001 0.521 0.521 ptest.py:14(twitter) 1 0.000 0.000 0.876 0.876 ptest.py:18(vk) 1 0.000 0.000 2.683 2.683 ptest.py:22(main) 1 0.002 0.002 0.543 0.543 ptest.py:6(facebook) |
Мы получим большой список результатов, давайте найдем только наши функции. Запуск всех сайтов выполнился в 2.68 секунды. Быстрее всех открылся Twitter, самый медленный стал VK. Это конечно не идеальный тест скорости, но наша задача была выполнена. Мы выясняли скорость открытия сайтов используя cProfile.
Подведем итоги
С этого момента вы должны знать, как использовать модуль cProfile для диагностики того, что делает ваш код таким медленным. Вы также можете взглянуть на модуль Python timeit. Он позволяет вам отсчитывать маленькие части вашего кода, если вы не хотите иметь дела со сложностями, которые может повлечь за собой профилирование. Также существует несколько других сторонних модулей, которые также хороши в профилировании как проекты line_profiler и memory_profiler.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»