Тестирование вашего кода на скорость и эффективность - важный аспект разработки программного обеспечения. Когда код занимает слишком много времени или потребляет слишком много ресурсов, таких как память или процессор, вы можете быстро столкнуться с широким кругом проблем. Машины, на которых работает ваш код, могут стать нестабильными, ваш код может вызвать непреднамеренные побочные эффекты, а в некоторых случаях даже потерю данных. Обязательно изучите очевидные проблемы производительности по мере их возникновения, но не менее важно установить базовые уровни и профили производительности.
Код должен быть протестирован на функциональность от начала до конца в процессе разработки, но также важно проверить производительность. Выработка хороших привычек тестирования вашего кода на такие вещи, как скорость и использование ресурсов во время его написания, избавит вас от головной боли в будущем.
В этой статье мы собираемся изучить способы, с помощью которых вы можете протестировать и базировать свой код Python. Библиотеки, которые мы рассмотрим, находятся в свободном доступе и предоставляют гибкие возможности для выполнения таких задач, как определение времени производительности, измерение потребления ресурсов и многое другое. Давайте начнем.
1. timeit
Во-первых, это утилита Python, которая существует уже некоторое время и широко популярна для выполнения быстрых тестов производительности. Давайте настроим простой тестовый скрипт и воспользуемся timeit.Timer
для запуска простого теста времени:
#!/usr/bin/env python3 # test.py import timeit import time def long_function(): print('function start') time.sleep(5) print('function end') print(timeit.Timer(long_function).timeit(number=2))
Внутри нашего long_function
мы вводим некоторую задержку, используя time.sleep
, чтобы имитировать некоторые длительные задачи. Затем, чтобы проверить нашу функцию, мы передаем ее timeit.Timer
. Класс Timer
измеряет общую скорость выполнения функции. Аргумент number
указывает, сколько раз следует повторить тест. Это полезно, если у вас есть функции, время выполнения которых может даже незначительно отличаться. Повторение теста позволит лучше понять скорость, потому что у вас будет больше данных для работы.
После выполнения приведенного выше кода вы должны получить следующий результат. Это показывает, что timeit
запустил нашу функцию дважды, а затем произвел общее время, которое потребовалось. Поскольку мы просто спим, наша дисперсия не будет слишком большой:
function start function end function start function end 10.00957689608913
Библиотека timeit
отлично подходит для выполнения быстрых изолированных тестов фрагментов кода, но также может запускаться из командной строки как автономный модуль и содержит еще более полезные параметры.
Ознакомьтесь с подробной документацией для получения дополнительной информации:
2. line_profiler
Следующая библиотека, которую мы рассмотрим, называется line_profiler
, и ее использование немного более уникально, чем другие решения. Библиотека line_profiler
позволяет получить время выполнения каждой отдельной строки в файле. Это невероятно полезно, если у вас возникли проблемы с сокращением числа медленных функций или сторонних вызовов в файле большего размера. Наблюдая за временем, потраченным на каждую строку, вы можете быстро выявить проблемы, а не копаться в строке за строкой плотного кода.
Стандартное использование line_profiler
сначала может показаться немного запутанным, но если вы воспользуетесь им несколько раз, это станет проще. Чтобы профилировать свой код, вам нужно добавить @profile
декораторов к каждой функции. Давайте еще раз воспользуемся нашим предыдущим примером и настроим его, чтобы увидеть, как это работает:
#!/usr/bin/env python3 # test.py import time @profile def long_function(): print('function start') time.sleep(5) print('function end') long_function()
Выглядит довольно просто, правда? Это потому, что с line_profiler
вам не нужно ничего импортировать или сильно менять код, вам просто нужно добавить декоратор. Поскольку у нас есть декоратор для нашей медленной функции, единственное, что осталось сделать, это проверить код. Чтобы запустить line_profiler
, вы должны сделать две вещи вне кода:
kernprof -l test.py python -m line_profiler test.py.lprof
Первая команда, приведенная выше, фактически запустит line_profiler
для вашего файла и сгенерирует отдельный .lprof
файл в том же каталоге. Этот .lprof
файл содержит результаты, по которым можно составить отчет с помощью самого модуля во второй команде. Давайте посмотрим на вывод второй команды:
Timer unit: 1e-06 s Total time: 5.00472 s File: test.py Function: long_function at line 6 Line # Hits Time Per Hit % Time Line Contents ============================================================== 6 @profile 7 def long_function(): 8 1 15.0 15.0 0.0 print('function start') 9 1 5004679.0 5004679.0 100.0 time.sleep(5) 10 1 21.0 21.0 0.0 print('function end')
Каждая строка профилированной функции приводится с подробной статистикой. Поскольку мы проводим так много времени в long_function
спящем режиме, это занимает почти 100% времени выполнения файла. Использование line_profiler
для генерации разбивки того, на что уходит все ваше время выполнения, позволяет вам быстро определить, где вам может потребоваться сосредоточиться на рефакторинге или ускорении медленных задач.
Ознакомьтесь с документацией для получения дополнительной информации:
3. ресурс
В следующих двух библиотеках мы собираемся переключить передачи и сосредоточиться на основных ресурсных последствиях запуска кода Python. Возможность определить, сколько процессора и памяти использует ваш код, практически обязательна в современной среде. Отключение неограниченных процессов для пережевывания циклов - табу, и вы можете погрузиться в мир боли. Вот тут-то и пригодится resource
. Эта библиотека позволит вам измерять использование ресурсов в вашем коде и даже устанавливать ограничения на то, какая часть конкретного ресурса может быть потреблена.
Давайте посмотрим на пример того, как проверить использование ЦП из сценария:
#!/usr/bin/env python3 # test.py import time from resource import getrusage, RUSAGE_SELF def long_function(): for i in range(10 ** 10): 2 + 2 long_function() print(getrusage(RUSAGE_SELF))
В этом примере мы снова изменили существующий сценарий, чтобы получить пакет resource
. Чтобы увеличить нагрузку на ЦП во время long_function
, мы перебираем большой диапазон чисел и заставляем ЦП выполнять некоторые вычисления. Это приведет к большей нагрузке, чем просто сон.
После завершения нашего теста мы должны увидеть следующий результат использования:
resource.struct_rusage(ru_utime=152.395004, ru_stime=0.035994, ru_maxrss=8536, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=1092, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=0, ru_nivcsw=1604)
Из этого вывода видно, что мы потратили довольно много циклов пользовательского процессора. Если вы посмотрите на цифру ru_utime
(время пользователя), она показывает, что мы потратили в общей сложности 152 секунды времени. Вы можете не только измерять время процессора, но и некоторые другие показатели, которые также дают вам представление о таких вещах, как блочный ввод-вывод и использование памяти стека.
Для получения более подробной информации ознакомьтесь с документацией:
4. memory_profiler
Библиотека memory_profiler
похожа на line_profiler
, но ориентирована на создание статистики, непосредственно связанной с использованием памяти. Когда вы запускаете memory_profiler
свой код, вы по-прежнему получаете разбивку по строкам, но с акцентом на общее и добавочное использование памяти по строкам.
Как и в line_profiler
, мы собираемся использовать ту же структуру декоратора для тестирования нашего кода. Вот модифицированный пример кода:
#!/usr/bin/env python3 # test.py @profile def long_function(): data = [] for i in range(100000): data.append(i) return data long_function()
В приведенном выше примере мы создаем тестовый список и помещаем в него большой диапазон целых чисел. Это медленно расширяет список, чтобы мы могли видеть, как использование памяти растет с течением времени. Чтобы просмотреть memory_profiler
отчет, вы можете просто запустить:
python -m memory_profiler test.py
Это должно создать следующий отчет, содержащий построчную статистику памяти:
Filename: tat.py Line # Mem usage Increment Occurences Line Contents ============================================================ 3 38.207 MiB 38.207 MiB 1 @profile 4 def long_function(): 5 38.207 MiB 0.000 MiB 1 data = [] 6 41.934 MiB 2.695 MiB 100001 for i in range(100000): 7 41.934 MiB 1.031 MiB 100000 data.append(i) 8 41.934 MiB 0.000 MiB 1 return data
Как видите, наша функция начинается примерно с 38MB
использования памяти и увеличивается до 41.9MB
после заполнения нашего списка. Хотя вы можете получить информацию об использовании памяти из библиотеки resource
, она не дает подробной построчной разбивки, как memory_profiler
. Если вы ищете утечку памяти или имеете дело с особенно раздутым приложением, это правильный путь.
Посетите memory_profiler
GitHub, чтобы узнать больше:
Спасибо за внимание! Надеюсь, вам понравилось копаться в этих великолепных библиотеках производительности Python. Напишите в Твиттере некоторые из ваших любимых способов тестирования и точной настройки кода Python!