Меню

Python измерение времени работы функции



Как работает timeit в Python?

Модуль Python timeit — это простой интерфейс для быстрого измерения времени выполнения небольших блоков кода.

Когда вы создаете приложение, вы можете задаться вопросом, как этот блок кода будет работать, и захотите протестировать его в различных сценариях.

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

Мы рассмотрим как интерфейс командной строки, так и вызываемый интерфейс.

Python timeit — интерфейс командной строки

Интерфейс командной строки очень похож на интерфейс запуска программы Python.

Вам необходимо импортировать внешний модуль timeit с помощью опции -m и применить его к вашему коду.

Это запустит фрагмент, переданный в виде строки, с использованием timeit .

По умолчанию это будет запускать код 1 миллион раз в Linux и 20 миллионов раз в Windows и измерять лучшее время среди этих значений. Ниже приведены результаты моей системы Linux.

Обратите внимание, что если у вас уже есть цикл for в вашем фрагменте, модуль гарантирует, что общее количество итераций близко к 1 миллиону, поэтому весь ваш цикл не будет выполняться 1 миллион раз.

Мы также можем использовать timeit через интерпретатор Python и импортировать его, используя:

Чтобы узнать время выполнения, передайте код в виде строки в timeit.timeit() .

Мы можем контролировать количество итераций с помощью параметра number .

Использование модуля

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

Что, если ваш код требует предварительной настройки? А если вам тоже нужно импортировать определенные модули?

Что ж, решение этой проблемы — использовать блок кода настройки, который выполнит всю необходимую работу по настройке всех необходимых модулей и переменных.

Написать блок настройки очень просто. Вы просто пишете любой код, который вам нужен, и передаете его в виде строки в переменную.

После этого вы можете написать свой основной блок кода и передать его timeit.timeit() , используя параметры setup и stmt .

timeit гарантирует, что настройка будет выполнена до измерения вашего основного цикла, поэтому он выполняется только один раз.

Этот код пытается получить все подмассивы из начального элемента массива numpy. Обратите внимание, что блок настройки запускается только один раз.

Сравните производительность блоков кода

Мы можем легко сравнить производительность нескольких блоков кода с помощью timeit .

Мы будем использовать для этой цели таймер, используя timeit.default_timer() .

Время, затрачиваемое на блок кода, будет текущим временем минус начальное время, взятое за эталон, которое вы можете передавать через переменные.

Давайте протестируем 2 функции в массиве numpy range() и np.arange() и посмотрим, как они сравниваются.

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

Время для конкретной функции

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

Источник

Как можно засечь время выполнения каждой из функций и суммарное время выполнения программы

Есть код с тремя сотрировками, есть отчет о времени выполнения программы. Как можно засечь время выполнения каждой из функций и суммарное время выполнения программы. Еще, если не сложно, посоветуйте как сократить программу.

4 ответа 4

Чтобы измерить время выполнения программы, можно time команду использовать (часто встроена в shell):

Чтобы посмотреть сколько времени индивидуальные функции занимают, можно cProfile модуль использовать:

В графическом виде результаты удобно в KCachegrind просматривать. Пример команд. Больше вариантов: How can you profile a script?

line_profiler позволяет построчно сравнение производить.

Содержание:

  • timeit
  • reporttime.py
  • make-figures.py
  • reporttime + pandas

timeit

Чтобы измерить производительность отдельной функции, можно timeit модуль использовать:

Тот же интерфейс предоставляет pyperf модуль (помимо прочего):

Для интерактивной работы можно %timeit magic в ipython/jupyter notebook использовать.

reporttime.py

Оптимизируя выполнение функции, стоит убедиться что она работает корректно (тесты), что изменения действительно ускорили её работу (сравнение производительности). Для этого можно pytest-benchmark использовать.

Для удобства сравнения производительности нескольких алгоритмов, можно автоматически соответствующие функции собрать по общему префиксу в имени ( get_functions_with_prefix() ). К примеру, если функции в вопросе можно назвать: sorted_selection , sorted_insertion , sorted_bubble и поместить в daedra.py файл:

где reporttime.py . measure() функция измеряет производительность функций похожим на python -mtimeit команду способом.

Результаты

Таблица показывает, что на уже отсортированном вводе sorted_insertion() функция заметно выигрывает (в этом случае линейное время для этой функции требуется по сравнению с квадратичным для sorted_selection() и sorted_bubble() ). Для случайного ввода, производительность примерно одинаковая. sorted_bubble() хуже во всех вариантах.

make-figures.py

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

Чтобы нарисовать время выполнения функций для разных вводов:

seq_range() , seq_random() задают два типа ввода (уже отсортированный и случайный соответственно). Можно определить дополнительные типы, определив seq_*(n) функцию. Пример запуска:

PYTHONPATH=. используется, чтобы make-figures.py смог найти plot_daedra модуль (с seq_range , seq_random функциями) в текущей директории. —maxn определяет наибольшее n , которое в seq_(n) функции передаётся.

Результаты

Рисунки подтверждают, что sorted_insertion() показывает линейное поведение на отсортированном вводе ( seq_range =0,1,2,3,4. n-1 ). И квадратичное на случайном вводе ( seq_random ). Коэффициент перед log2(N) показывает приближённо соответствующую степень в функции роста алгоритма в зависимости от размера ввода:

reporttime + pandas

Собрав результаты измерений времени выполнения функций сортировки из daedra.py ( sorted_*() ) для разных типов (уже отсортированный/случайный) и размеров ввода (длины от 1 до 100000):

Удобно исследовать результаты интерактивно, используя pandas.DataFrame :

К примеру, чтобы сравнить поведение функций на уже отсортированном вводе:

Поведение на случайном вводе:

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

Источник

Как измерить время выполнения скрипта Python

Допустим, вы хотите знать время выполнения следующего кода Python:

Есть несколько способов измерить время, необходимое для выполнения скрипта Python, но вот лучший способ сделать это, и я объясню почему:

В консоли получим: 0.01137321546

Это вывод, который я получаю на своем Macbook Pro. Итак, это более или менее 1/100 секунды.

Как работает вышеуказанный скрипт

Строка 1: мы импортируем модуль timeit . Строка 3: мы создаем переменную. В этой переменной мы храним код, который хотим протестировать. Этот код должен идти внутри тройных кавычек. Итак, тестовый код предоставляется в виде строки. Строка 10: мы вызываем функцию time.timeit() . Функция timeit() получает тестовый код в качестве аргумента, выполняет его и записывает время выполнения. Чтобы получить точное время, я приказал timeit() выполнить 100 циклов. Поэтому мне пришлось разделить вывод на 100, чтобы получить время выполнения только для одного цикла. Строка 11: мы просто распечатываем время выполнения. Результат — время выполнения в секундах.

Почему timeit() — лучший способ измерить время выполнения кода Python?

1. timeit() автоматически будет использовать time.clock() или time.time() для вас в фоновом режиме, в зависимости от того, какая операционная система вам нужна для получения наиболее точных результатов.

2. timeit() отключает сборщик мусора, который может исказить результаты.

3. timeit() повторяет тест много раз (в нашем случае 100 раз), чтобы минимизировать влияние других задач, выполняемых в вашей операционной системе.

Упражнение:

Кстати, код, который мы тестировали выше, строит список путем умножения элементов другого списка. Я могу достичь того же результата, используя range :

Если вам больше нечем заняться и попробуйте выполнить упражнение, попробуйте измерить время выполнения приведенного выше кода с помощью timeit() .

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

Источник

Функции тайминга Python: три способа контролировать ваш код

Хотя многие разработчики признают Python эффективным языком программирования, программы на чистом Python могут работать медленнее, чем их аналоги на скомпилированных языках, таких как C, Rust и Java. В этом руководстве вы узнаете, как использовать таймеры Python для отслеживания скорости выполнения ваших программ.

В этом уроке вы узнаете, как использовать:

  • time.perf_counter() для измерения времени
  • Классы для сохранения состояния
  • Контекстные менеджеры для работы с блоком кода
  • Декораторы для настройки функций

Вы также получите базовые знания о том, как работают классы, контекстные менеджеры и декораторы. Поскольку будут приведены примеры каждой концепции, вы сможете по желанию использовать одну или несколько из них в своем коде, как для замера времени выполнения кода, так и для других применений. Каждый метод содержит свои преимущества, и вы узнаете, какие из них использовать в зависимости от ситуации. Кроме того, у вас будет рабочий таймер Python, который вы можете использовать для мониторинга ваших программ!

Таймеры Python

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

Функции таймера Python

Если вы посмотрите на встроенный модуль time в Python, то заметите несколько функций, которые могут измерять время:

Python 3.7 ввел несколько новых функций, таких как thread_time(), а также наносекундные версии всех вышеперечисленных функций, именуемых с суффиксом _ns. Например, perf_counter_ns() — это наносекундная версия perf_counter(). Подробности об этих функциях будет расказано позже. А пока обратите внимание на то, что говорится в документации о perf_counter():

Возвращает значение (в долях секунд) счетчика производительности, то есть часов с самым высоким доступным разрешением для измерения короткого промежутка времени.

Во-первых, вы будете использовать perf_counter() для создания таймера Python. Позже вы сравните это с другими функциями таймера Python и узнаете, почему perf_counter() обычно является лучшим выбором.

Пример: Последовательность Фибоначчи

Чтобы лучше сравнить различные способы добавления таймера Python к своему коду, вы будете применять разные функции таймера Python к одному и тому же примеру кода в этом руководстве. Если у вас уже есть код, который вы хотели бы измерить, смело следуйте этим примерам.

Пример, который вы увидите в этом учебнике — это простая итеративная функция, которая вычисляет число по номеру в последовательности Фибоначчи.

Числа Фибоначчи – это ряд чисел, в котором каждое следующее число равно сумме двух предыдущих: 1, 1, 2, 3, 5, 8, 13, . . Иногда ряд начинают с нуля: 0, 1, 1, 2, 3, 5, . . В данном случае мы будем придерживаться первого варианта.

Вычисление n-го числа ряда Фибоначчи с помощью цикла while:

Сохраним пример в файле с именем fibo.py. Код состоит из одной функции, которая вычисляет и печатает n элемент последовательности.

Ваш первый таймер Python

Давайте добавим в пример простой Python-таймер с помощью time.perf_counter(). Опять же, это счетчик производительности, который хорошо подходит для замеров времени выполнения частей кода.

perf_counter() измеряет время в секундах с некоторого неопределенного момента времени, что означает, что возвращаемое значение одного вызова функции бесполезно. Однако, когда вы посмотрите на разницу между двумя вызовами perf_counter(), вы сможете выяснить, сколько секунд прошло между двумя вызовами:

В этом примере вы сделали два вызова perf_counter() с интервалом почти 4 секунды. Вы можете подтвердить это, рассчитав разницу между двумя выходами: 32315.26 — 32311.49 = 3.77.

Теперь вы можете добавить таймер Python к коду примера:

Обратите внимание, что perf_counter() вызывается как до, так и после вычисления значения функции. Затем печатается время, необходимое для вычисления, вычисляя разницу между двумя вызовами.

Примечание: в строке print(f»Вычисление заняло секунд») буква f перед строкой указывает на то, что это f-строка, что является удобным способом форматирования текстовой строки. : :0.4f — это спецификатор формата, который говорит, что число toc — tic должно быть напечатано как десятичное число с четырьмя десятичными знаками.

f-строки доступны только в Python 3.6 и более поздних версиях. Для получения дополнительной информации ознакомьтесь с официальной документацией Python 3.

Теперь, когда вы запустите пример, вы увидите потраченное время на вычисления:

Вы рассмотрели основы тайминга своего кода Python. В оставшейся части руководства вы узнаете, как можно обернуть Python-таймер в класс, менеджер контекста и декоратор, чтобы сделать его более консистентным и удобным в использовании.

Python класс Timer

Посмотрите, как вы добавили таймер Python в приведенный выше пример. Обратите внимание, что вам нужна хотя бы одна переменная (tic) для хранения состояния таймера Python перед началом вычислений. Теперь создадим класс, который будет выполнять те же действия, что и ручные вызовы perf_counter(), но более читабельно и консистентно.

В этом руководстве вы создадите и обновите класс Timer, который вы можете использовать для определения таймингов кода несколькими различными способами. Окончательный код также доступен в PyPI под названием codetiming. Вы можете установить в вашу систему следующим образом:

Понимание классов в Python

Классы являются основными строительными блоками объектно-ориентированного программирования. Класс — это шаблон, который вы можете использовать для создания объектов. Хотя Python не заставляет вас программировать объектно-ориентированным способом, классы используются повсюду в языке. Для быстрого доказательства давайте рассмотрим модуль time:

type() возвращает тип объекта. Здесь вы можете видеть, что модули на самом деле являются объектами, созданными из класса module. Специальный атрибут .__class__ используется для получения доступа к классу, который определяет объект. На самом деле, почти все в Python — это класс:

В Python классы отлично подходят, когда вам нужно смоделировать что-то, что должно отслеживать определенное состояние. В общем случае класс — это набор свойств (называемых атрибутами) и поведений (называемых методами).

Создание класса таймера Python

Классы хороши для отслеживания состояния. В классе Timer вы хотите отслеживать, когда запускается таймер и сколько времени прошло с момента реализации. Для первой реализации Timer создадим атрибут ._start_time, а также методы .start() и .stop(). Добавим следующий код в файл с именем timer.py:

Здесь происходит несколько разных вещей, поэтому давайте пройдемся по коду шаг за шагом.

В строке 5 вы определяете класс TimerError. Обозначение (Exception) означает, что TimerError наследует от другого класса с именем Exception. Python использует этот встроенный класс для обработки ошибок. Вам не нужно добавлять какие-либо атрибуты или методы в TimerError. Тем не менее, наличие пользовательской ошибки даст вам больше гибкости для решения проблем внутри Timer.

Определение самого Timer начинается в строке 8. Когда вы впервые создаете или создаете экземпляр объекта из класса, ваш код вызывает специальный метод .__init__(). В этой первой версии Timer вы инициализируете только атрибут ._start_time, который вы будете использовать для отслеживания состояния вашего таймера Python. Он имеет значение None, когда таймер не работает. Когда таймер работает, ._start_time отслеживает, когда таймер был запущен.

Примечание: префикс подчеркивания ._start_time является соглашением Python. Он сигнализирует о том, что ._start_time является внутренним атрибутом, которым не должны манипулировать пользователи класса Timer.

Когда вызывается .start() для запуска нового таймера Python, сначала проверяется, что таймер еще не запущен. Затем сохраняется текущее значение perf_counter() в ._start_time. С другой стороны, когда вызывается .stop(), вы сначала проверяете, работает ли таймер Python. Если это так, то вы вычисляете истекшее время как разницу между текущим значением perf_counter() и тем, которое было сохранено в ._start_time. Наконец, сбрасыватся ._start_time, чтобы таймер мог быть перезапущен, и распечатать истекшее время.

Использование класса Timer:

Сравните это с предыдущим примером, где вы использовали perf_counter() напрямую. Структура кода довольно похожа, но теперь код стал более понятным, и это является одним из преимуществ использования классов. Тщательно выбирая имена классов, методов и атрибутов, вы можете сделать свой код очень информативным!

Использование класса Timer Python

Давайте применим таймер к series_number.py. Вам нужно всего лишь внести несколько изменений в свой предыдущий код:

Обратите внимание, что код очень похож на то, что вы видели ранее. В дополнение к тому, чтобы сделать код более читабельным, Timer заботится о печати прошедшего времени на консоль, что делает логгирование затраченного времени более последовательным. Когда вы запустите код, вы увидите примерно такой же вывод:

Печать прошедшего времени из Timer может быть последовательной, но, похоже, этот подход не очень гибкий. В следующем разделе вы увидите, как настроить свой класс.

Добавление большего удобства и гибкости

До сих пор вы видели, что классы подходят для случаев, когда вы хотите инкапсулировать состояние и обеспечивать согласованное поведение в вашем коде. В этом разделе вы добавим больше удобств и гибкости вашему таймеру Python:

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

Во-первых, давайте посмотрим, как вы можете настроить текст, используемый для отчета о затраченном времени. В предыдущем коде текст f»Вычисление заняло секунд» жестко закодирован в .stop(). Вы можете добавить гибкость классам, используя переменные экземпляра. Их значения обычно передаются в качестве аргументов .__init__() и сохраняются как собственные атрибуты. Для удобства вы также можете предоставить разумные значения по умолчанию.

Чтобы добавить .text в качестве переменной экземпляра Timer, изменим код:

Обратите внимание, что текст по умолчанию «Вычисление заняло секунд» передаётся как обычная строка, а не как f-строка. Вы не можете использовать f-строку здесь, потому что они оцениваются немедленно, и когда вы создаете экземпляр Timer, ваш код еще не вычислил истекшее время.

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

В .stop() используется .text в качестве шаблона и .format() для заполнения шаблона:

После обновления timer.py вы можете изменить текст следующим образом:

Далее, предположим, что нужно не просто напечатать сообщение в консоль. Может быть, вы хотите сохранить ваши измерения времени в базе данных. Вы можете сделать это, возвращая значение elapsed_time из .stop(). Затем вызывающий код может либо игнорировать это возвращаемое значение, либо сохранить его для дальнейшей обработки.

Возможно, вы хотите интегрировать Timer в свои процедуры логгирования. Для поддержки логгирования или других выходов из Timer вам нужно изменить вызов print(), чтобы пользователь мог предоставить свою собственную функцию логгирования. Это можно сделать аналогично тому, как ранее настраивался текст:

Вместо непосредственного использования print() создана другая переменная экземпляра self.logger, которая должна ссылаться на функцию, которая принимает строку в качестве аргумента. В дополнение к print() вы можете использовать такие функции, как logging.info() или .write() для файловых объектов. Также обратите внимание на тест if, который позволяет полностью отключить печать, передав команду logger = None.

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

Когда вы запускаете эти примеры в интерактивной оболочке, Python автоматически печатает возвращаемое значение.

Третье улучшение, которое будет добавлено — это возможность накапливать измерения времени. Это может понадобиться, когда вызывается медленная функция в цикле. Добавим немного больше функциональности в виде именованных таймеров со словарем, который отслеживает каждый таймер Python в коде.

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

Код перебирает числа от 0 до 9 и использует их в качестве аргументов смещения для fibo(). Когда вы запустите скрипт, вы увидите много информации, напечатанной на вашей консоли:

Одна тонкая проблема с этим кодом заключается в том, что вы измеряете не только время, необходимое для вычисления элемента последовательности, но и время, которое Python тратит на печать результатов на экран. Это может быть не так важно, поскольку время, потраченное на печать, должно быть незначительным по сравнению со временем, потраченным на вычисления. Тем не менее, было бы хорошо иметь возможность точно определить время.

Есть несколько способов обойти это без изменения текущей реализации Timer. Однако поддержка этого варианта использования будет весьма полезна и может быть выполнена всего несколькими строками кода.

Во-первых, добавим словарь .timers в качестве переменной класса в Timer, что означает, что все экземпляры Timer будут использовать его совместно. Реализуется это, определяя словарь вне любых методов:

Переменные класса могут быть доступны либо непосредственно в классе, либо через экземпляр класса:

В обоих случаях код возвращает один и тот же пустой словарь классов.

Затем добавим дополнительные имена к вашему таймеру Python. Вы можете использовать имя для двух разных целей:

  1. Поиск истекшего времени в вашем коде
  2. Накопление таймеров с тем же именем

Чтобы добавить имена к вашему таймеру Python, вам нужно внести еще два изменения в timer.py. Во-первых, Timer должен принять имя в качестве параметра. Во-вторых, истекшее время должно быть добавлено к .timers, когда таймер останавливается:

Обратите внимание, что используется .setdefault() при добавлении нового таймера Python в .timers. Это отличная функция, которая устанавливает значение только в том случае, если имя еще не определено в словаре. Если имя уже используется в .timers, то значение остается без изменений. Это позволяет накапливать несколько таймеров:

Теперь вернёмся к series_numbers.py и убедиться, что измеряется только время, потраченное на вычисления:

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

Последнее улучшение, которое внесём в Timer — это сделать его более информативным, когда вы работаете с ним в интерактивном режиме. Попробуем следующее:

Эта последняя строка является способом, которым Python представляет объекты по умолчанию. Хотя вы можете почерпнуть из него некоторую информацию, она обычно не очень полезна. Вместо этого было бы неплохо увидеть такие вещи, как имя Timer или как он будет сообщать о времени.

В Python 3.7 классы данных были добавлены в стандартную библиотеку. Они обеспечивают несколько удобств для ваших классов, включая более информативную строку представления.

Примечание: классы данных включены в Python только для версии 3.7 и выше. Однако в PyPI для Python 3.6 имеется бэкпорт.

Вы можете установить его используя pip:

Конвертируем таймер Python в класс данных, используя декоратор @dataclass. Вы узнаете больше о декораторах позже в этом уроке. Сейчас можно представить это как о нотации, которая сообщает Python, что Timer является классом данных:

Этот код заменяет предыдущий метод .__init__(). Обратите внимание, как классы данных используют синтаксис, который выглядит аналогично синтаксису переменных класса, который вы видели ранее для определения всех переменных. Фактически, .__init__() создается автоматически для классов данных на основе аннотированных переменных в определении класса.

Вы должны аннотировать свои переменные, чтобы использовать класс данных. Вы можете использовать это, чтобы добавить подсказки к вашему коду. Если вы не хотите использовать подсказки типов, вместо этого вы можете аннотировать все переменные с помощью Any, как вы делали выше. Вскоре вы увидите, как добавить фактические подсказки типов в ваш класс данных.

Вот несколько заметок о классе данных Timer:

  • Строка 4: декоратор @dataclass определяет Timer как класс данных.
  • Строка 6: Специальная аннотация ClassVar необходима для классов данных, чтобы указать, что .timers является переменной класса.
  • Строки с 7 по 9: .name, .text и .logger будут определены как атрибуты на «Timer, значения которых могут быть указаны при создании экземпляров Timer. Все они имеют заданные значения по умолчанию.
  • Строка 10: напомним, что ._start_time — это специальный атрибут, который используется для отслеживания состояния таймера Python, но должен быть скрыт от пользователя. Используя dataclasses.field(), вы говорите, что ._start_time следует удалить из .__init__() и представления Timer.
  • Строки с 12 по 15: Используется специальный метод .__post_init__() для любой инициализации, которую вам нужно выполнить, кроме установки атрибутов экземпляра. Здесь он используется для добавления именованных таймеров в .timers.

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

Теперь у вас есть довольно аккуратная версия Timer, которая последовательна, гибка, удобна и информативна! Многие из улучшений, которые вы видели в этом разделе, могут быть применены и к другим типам классов в ваших проектах.

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

Использование класса для создания таймера, Python предлагает несколько преимуществ:

  • Удобочитаемость: ваш код будет читаться более естественно, если вы тщательно выберете имена классов и методов.
  • Согласованность: ваш код будет легче использовать, если вы инкапсулируете свойства и поведение в атрибуты и методы.
  • Гибкость: ваш код будет многократно использоваться, если вы будете использовать атрибуты со значениями по умолчанию вместо жестко закодированных значений.

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

Менеджер контекста Python Timer

Python класс Timer прошел долгий путь! По сравнению с первым созданным таймером Python код стал достаточно мощным. Тем не менее, для использования таймера все еще есть немного стандартного кода:

  1. Во-первых, создать экземпляр класса.
  2. Вызвать .start() перед тем блоком кода, который вы хотите синхронизировать.
  3. Вызовать .stop() после блока кода.

К счастью, в Python есть уникальная конструкция для вызова функций до и после блока кода — менеджер контекста. В этом разделе вы узнаете, что такое контекстные менеджеры и как вы можете создавать свои собственные. Затем вы увидите, как расширить Timer, чтобы он мог работать и в качестве менеджера контекста. Наконец, вы увидите, как использование Timer в качестве менеджера контекста который упростит ваш код.

Понимание контекстных менеджеров в Python

Менеджеры контекста были частью Python в течение долгого времени. Они были представлены PEP 343 в 2005 году и впервые реализованы в Python 2.5. Вы можете распознать контекстные менеджеры в коде с помощью ключевого слова with:

EXPRESSION — это некоторое выражение Python, которое возвращает менеджер контекста. Менеджер контекста необязательно связан с именем VARIABLE. BLOCK — это любой обычный блок кода Python. Менеджер контекста гарантирует, что ваша программа вызывает некоторый код перед BLOCK, а другой — после выполнения BLOCK. Последнее произойдет, даже если BLOCK вызовет исключение.

Наиболее распространенное использование контекстных менеджеров, вероятно, обработка различных ресурсов, такие как файлы, блокировки и соединения с базой данных. Затем менеджер контекста используется для освобождения и очистки ресурса после его использования. В следующем примере раскрывается фундаментальная структура timer.py путем печати только строк, содержащих двоеточие. Что еще более важно, он показывает общую идиому для открытия файла в Python:

Обратите внимание, что fp, указатель файла, никогда не закрывается явно, потому что использовался open() в качестве менеджера контекста. Можно подтвердить, что fp закрылся автоматически:

В этом примере open(«timer.py») — это выражение, которое возвращает менеджер контекста. Этот менеджер контекста связан с именем fp. Диспетчер контекста действует во время выполнения print(). Этот однострочный кодовый блок выполняется в контексте fp.

Что это значит, что fp является контекстным менеджером? Технически это означает, что fp реализует протокол менеджера контекста. В основе языка Python лежит много разных протоколов. Вы можете думать о протоколе как о контракте, в котором указано, какие конкретные методы должен реализовывать ваш код.

Протокол менеджера контекста состоит из двух методов:

  1. Вызов .__enter__() при входе в контекст, связанный с менеджером контекста.
  2. Вызов .__exit__() при выходе из контекста, связанного с менеджером контекста.

Другими словами, чтобы создать менеджер контекста самостоятельно, вам нужно написать класс, который реализует .__enter__() и .__exit__(). Ни больше ни меньше. Давайте попробуем Hello, World! пример менеджера контекста:

Greeter — менеджер контекста, потому что он реализует протокол менеджера контекста. Вы можете использовать его так:

Во-первых, обратите внимание, как .__enter__() вызывается до того, как вы что-то делаете, а .__exit__() вызывается после. В этом упрощенном примере мы не ссылаемся на менеджер контекста. В таких случаях вам не нужно указывать менеджеру контекста имя с as.

Затем обратите внимание, как .__enter__() возвращает self. Возвращаемое значение .__enter__() — это то, с чем связано as. Обычно вы хотите вернуть self из .__enter__() При создании менеджеров контекста. Вы можете использовать это возвращаемое значение следующим образом:

Наконец, .__exit__() принимает три аргумента: exc_type, exc_value и exc_tb. Они используются для обработки ошибок в менеджере контекста и отражают возвращаемые значения sys.exc_info(). Если во время выполнения блока возникает исключение, то ваш код вызывает .__exit__() с типом исключения, экземпляром исключения и объектом трассировки. Часто вы можете игнорировать их в вашем менеджере контекста, и в этом случае .__exit__() вызывается перед повторным вызовом исключения:

Вы можете увидеть, что «See you later, Rascal» печатается, даже если в коде есть ошибка.

Теперь вы знаете, что такое контекстные менеджеры и как вы можете создать свой собственный. Если вы хотите погрузиться глубже, то посмотрите contextlib в стандартной библиотеке. Он включает в себя удобные способы определения новых контекстных менеджеров, а также готовые контекстные менеджеры, которые можно использовать для закрытия объектов, устранения ошибок или даже бездействия!

Создание менеджера контекста Python Timer

Вы увдели, как работают контекстные менеджеры в целом, но как они могут помочь с Timer кодом? Если вы можете запускать определенные функции до и после блока кода, вы можете упростить работу таймера Python. До сих пор вам нужно было явно вызывать .start() и .stop() при синхронизации вашего кода, но менеджер контекста может сделать это автоматически.

Опять же, чтобы Timer работал в качестве менеджера контекста, он должен придерживаться протокола менеджера контекста. Другими словами, он должен реализовывать .__enter__() и .__exit__() для запуска и остановки таймера Python. Все необходимые функции уже доступны, поэтому вам не нужно писать много нового кода. Просто добавьте следующие методы в класс Timer:

Таймер теперь контекстный менеджер. Важной частью реализации является то, что .__enter__() вызывает .start() для запуска таймера Python при вводе контекста, и .__exit__() использует .stop() для остановки таймера Python, когда код покидает контекст. Проверим это:

Вы также должны отметить еще две тонкие детали:

  1. .__enter__() возвращает self, экземпляр Timer, который позволяет пользователю связать экземпляр Timer с переменной, используя as. Например, with Timer() as t: создаст переменную t, указывающую на объект Timer.
  2. .__exit__() ожидает тройку аргументов с информацией о любом исключении, которое произошло во время выполнения контекста. В вашем коде эти аргументы упакованы в кортеж с именем exc_info и затем игнорируются, что означает, что Timer не будет пытаться обрабатывать какие-либо исключения.

.__exit__() в этом случае не обрабатывает ошибки. Тем не менее, одна из замечательных особенностей контекстных менеджеров заключается в том, что они гарантированно вызывают .__exit__(), независимо от того, как выходит контекст. В следующем примере намеренно создадим ошибку путем деления на ноль:

Обратите внимание, что Timer выводит истекшее время, даже если код не работает. Можно проверять и подавлять ошибки в .__exit__(). Смотрите документацию для получения дополнительной информации.

Использование менеджера контекста Python Timer

Давайте посмотрим, как использовать менеджер контекста Timer для определения времени вычисления числа Фибоначчи. Вспомните, как вы использовали Timer ранее:

Теперь замерим вызов fibo(1000). Воспользуемся менеджером контекста, чтобы сделать код короче, проще и более читабельным:

Этот код делает практически то же, что и код выше. Основное отличие состоит в том, что вы не определяете постороннюю переменную t, которая поддерживает чистоту вашего пространства имен.

Запуск скрипта должен дать знакомый результат:

Есть несколько преимуществ для добавления возможностей менеджера контекста к вашему классу таймера Python:

  • Незначительные усилия: вам нужно только одну дополнительную строку кода, чтобы рассчитать время выполнения блока кода.
  • Удобочитаемость: вызов менеджера контекста удобочитаем, и вы можете более четко визуализировать измеряемый блок кода.

Использование Timer в качестве менеджера контекста почти так же гибко, как и непосредственное использование .start() и .stop(), хотя в нем меньше стандартного кода. В следующем разделе вы увидите, как можно использовать Timer в качестве декоратора. Это облегчит мониторинг времени выполнения полных функций.

Python Timer декоратор

Ваш класс Timer теперь очень универсален. Однако есть один вариант использования, где он может быть еще более упорядоченным. Скажем, вы хотите отслеживать время, проведенное внутри одной данной функции в вашей кодовой базе. Используя контекстный менеджер, у вас есть два основных варианта:

  1. Используйте Таймер каждый раз, когда вы вызываете функцию:

Если вы вызовете do_something() во многих местах, это станет громоздко и сложно в обслуживании.

  1. Обернём код функцией содержащей внутри контекстный менеджер:

Timer нужно добавить только в одном месте, но это добавляет уровень отступа ко всему определению do_something().

Лучшее решение — использовать Timer` в качестве декоратора. Декораторы — это мощные конструкции, которые вы используете для изменения поведения функций и классов. В этом разделе вы узнаете немного о том, как работают декораторы, как можно расширить «Timer, чтобы стать декоратором, и как это упростит функции синхронизации.

Понимание декораторов в Python

Декоратор — это функция, которая оборачивает другую функцию, чтобы изменить ее поведение. Эта техника возможна, потому что функции являются объектами первого класса в Python. Другими словами, функции можно назначать переменным и использовать в качестве аргументов для других функций, как и любой другой объект. Это дает вам большую гибкость и является основой для некоторых из более мощных функций Python.

В качестве первого примера создадим декоратор, который ничего не делает:

Во-первых, обратите внимание, что turn_off() — это обычная функция. Что делает её декоратором, так это то, что она принимает функцию в качестве единственного аргумента и возвращает функцию. Вы можете использовать её, чтобы изменить другие функции:

Строка print = turn_off(print) декорирует инструкцию print с помощью декоратора turn_off(). По сути, он заменяет print() на lambda *args, **kwargs: None не возвращается turn_off(). Инструкция lambda представляет анонимную функцию, которая ничего не делает, кроме возврата None.

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

multiplier() является внутренней функцией, определенной внутри create_multiplier(). Обратите внимание, что у вас есть доступ к factor внутри multiplier(), в то время как multiplier() не определен вне create_multiplier():

Вместо этого create_multiplier() используется для создания новых функций умножения, каждая из которых основана на различном factor:

Точно так же вы можете использовать внутренние функции для создания декораторов. Помните, декоратор — это функция, которая возвращает функцию:

triple() является декоратором, потому что это функция, которая ожидает функцию как единственный аргумент, func(), и возвращает другую функцию, wrapper_triple(). Обратите внимание на структуру самого triple():

  • Строка 1 запускает определение triple() и ожидает функцию в качестве аргумента.
  • Строки 2-5 определяют внутреннюю функцию wrapper_triple().
  • Строка 6 возвращает wrapper_triple().

Этот шаблон распространен для определения декораторов. Интересные части — это те, что происходят внутри внутренней функции:

  • Строка 2 запускает определение wrapper_triple(). Эта функция заменит ту, которую выполняет функция triple(). Параметры *args и **kwargs , которые собирают позиционные и ключевые аргументы, которые вы передаете функции. Это дает вам гибкость в использовании triple() для любой функции.
  • В строке 3 выводится имя оформленной функции, и обратите внимание, что к ней применен triple().
  • Строка 4 вызывает func(), функцию, которая была оформлена с помощью triple(). Он передает все аргументы, передаваемые в wrapper_triple().
  • Строка 5 утраивает возвращаемое значение func() и возвращает его.

Давайте проверим это! knock() — это функция, которая возвращает слово World. Посмотрите, что произойдет, если оно утроится:

Умножение текстовой строки на число является формой повторения, поэтому World повторяется три раза. Декорирование происходит при knock = triple(knock).

Код кажется немного неуклюжим, чтобы продолжать повторять knock. Вместо этого в PEP 318 введен более удобный синтаксис для применения декораторов. Следующее определение knock() делает то же самое, что и выше:

Символ @«используется для применения декораторов. В этом случае «@triple означает, что triple() применяется к функции, определенной сразу после нее.

@functools.wraps — один из немногих декораторов, определенных в стандартной библиотеке. Это очень полезно при определении ваших собственных декораторов. Поскольку декораторы эффективно заменяют одну функцию другой, они создают тонкую проблему с вашими функциями:

@triple декорирует knock(), который затем заменяется внутренней функцией wrapper_triple(), как подтверждает вывод выше. Это также заменит имя, docstring и другие метаданные. Часто это не будет иметь большого эффекта, но может затруднить интроспекцию.

Иногда декорированные функции должны иметь правильные метаданные. @functools.wraps исправляют именно эту проблему:

С новым определением @triple метаданные сохраняются:

Обратите внимание, что knock() теперь сохраняет свое собственное имя, даже после того, как был декорирован. Это хорошая форма, чтобы использовать @functools.wraps всякий раз, когда вы определяете декоратор. Схема, которую вы можете использовать для большинства ваших декораторов, выглядит следующим образом:

Создание декоратора Timer Python

В этом разделе вы узнаете, как расширить свой таймер Python, чтобы вы также могли использовать его в качестве декоратора. Однако в качестве первого упражнения давайте создадим Python декоратор Timer с нуля.

Основываясь на приведенной выше схеме, вам нужно только решить, что делать до и после вызова декорированной функции. Это похоже на соображения о том, что делать при входе и выходе из контекстного менеджера. Вы хотите запустить таймер Python перед вызовом декорированной функции и остановить таймер Python после завершения вызова. Декоратор @timer может быть определен следующим образом:

Обратите внимание, насколько wrapper_timer() напоминает ранний шаблон, установленный вами для замеров кода Python. Вы можете применить @timer следующим образом:

Напомним, что вы также можете применить декоратор к ранее определенной функции:

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

@timer делает свою работу. Тем не менее, в некотором смысле вы вернулись к исходной точке, поскольку @timer не обладает никакой гибкостью или удобством Timer. Можете ли вы также заставить свой класс Timer действовать как декоратор?

До сих пор вы использовали декораторы как функции, применяемые к другим функциям, но это не совсем правильно. Декораторы должны быть вызываемыми. В Python есть много вызываемых типов. Вы можете сделать свои собственные объекты вызываемыми, определив специальный объект метод .__call__() в своем классе. Аналогично ведут себя следующие функции и классы:

Здесь square-это экземпляр, который может быть вызван и может содержать квадрат числа, точно так же, как функция square() в первом примере.

Это дает вам возможность добавить возможности декоратора к существующему классу таймера:

.__call__() использует тот факт, что Timer уже является контекстным менеджером, чтобы воспользоваться преимуществами, которые вы уже определили там. Убедитесь, что вы также импортируете functools в верхней части timer.py.

Теперь вы можете использовать таймер в качестве декоратора:

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

Основываясь на этих сходствах, в стандартной библиотеке есть класс mixin, называемый ContextDecorator. Вы можете добавить способности декоратора в свои классы контекстного менеджера просто унаследовав ContextDecorator:

Когда вы используете ContextDecorator таким образом, нет необходимости реализовывать его .__call__() самостоятельно, чтобы вы могли безопасно удалить его из класса Timer.

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

Давайте повторим series_number.py, используя таймер Python в качестве декоратора:

Если вы сравните эту реализацию с оригинальной реализацией без какого-либо времени, то заметите, что единственными различиями являются импорт Timer в строке 3 и применение @Timer() в строке 6. Существенным преимуществом использования декораторов является то, что они обычно просты в применении, как вы видите.

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

Расположение выходных данных прошедшего времени является предательским признаком того, что ваш код также учитывает время, необходимое для печати времени. Как вы видите здесь, ваш код печатает прошедшее время после вычислений.

При использовании таймера в качестве декоратора вы увидите те же преимущества, что и при использовании контекстных менеджеров:

  • Малое усилие: вам нужна только одна дополнительная строка кода, чтобы определить время выполнения функции.
  • Читабельность: когда вы добавляете декоратор, вы можете более четко отметить, что ваш код будет определять время выполнения функции.
  • Согласованность: вам нужно добавить декоратор только тогда, когда функция определена. Ваш код будет последовательно отсчитывать время при каждом вызове.

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

Код Таймера Python

Вы можете использовать код самостоятельно, сохранив его в файле с именем timer.py и импортировать его в вашу программу. Запустим новый таймер в качестве менеджера контекста:

>> from timer import Timer —>

Кроме этого codetiming.Timer работает точно так же, как timer.Timer. Подводя итог, можно использовать время тремя различными способами:

Этот вид таймера Python в основном полезен для мониторинга времени, которое ваш код тратит на отдельные ключевые блоки кода или функции. В следующем разделе вы получите краткий обзор альтернатив, которые можно использовать, если вы хотите оптимизировать свой код.

Другие функции таймеры в Python

Существует множество вариантов замеров выполнения вашего кода Python. В этом уроке вы узнаете, как создать гибкий и удобный класс, который можно использовать несколькими различными способами. Быстрый поиск по PyPI показывает, что уже существует множество проектов, предлагающих решения тайминга Python.

В этом разделе вы сначала узнаете больше о различных функциях, доступных в стандартной библиотеке для измерения времени, и о том, почему perf_counter() предпочтительнее. Затем вы увидите альтернативы оптимизации вашего кода, для которых таймер не очень хорошо подходит.

Использование альтернативных функций таймеров в Python

Вы использовали perf_counter() на протяжении всего этого урока для выполнения фактических измерений времени, но библиотека time Python поставляется с несколькими другими функциями, которые также измеряют время. Вот некоторые альтернативы:

Одна из причин, почему существует несколько функций, заключается в том, что Python представляет время как float. Числа с плавающей запятой по своей природе неточны. Возможно, вы уже видели подобные результаты раньше:

Float Python следует стандарту IEEE 754 для арифметики с плавающей запятой, который пытается представить все числа с плавающей запятой в 64 битах. Поскольку существует бесконечно много чисел с плавающей запятой, вы не можете выразить их в виде конечного числа битов.

IEEE 754 предписывает систему, в которой плотность чисел, которые вы можете представить, изменяется. Чем ближе вы к 1, тем больше чисел вы можете представить. Для больших чисел есть больше пространства между числами, которые вы можете выразить. Это имеет некоторые последствия, когда вы используете float для представления времени.

Рассмотрим time(). Основная цель этой функции-представить реальное время прямо сейчас. Он делает это как число секунд, прошедших с данного момента времени, называемого эпохой. Число, возвращаемое функцией time(), довольно велико, а это означает, что доступных чисел становится меньше, и разрешение страдает. В частности, time() не способен измерять наносекундные различия:

Наносекунда — это одна миллиардная секунды. Обратите внимание, что добавление наносекунды к t не влияет на результат. perf_counter(), с другой стороны, использует некоторый неопределенный момент времени в качестве своей эпохи, что позволяет ему работать с меньшими числами и, следовательно, получать лучшее разрешение:

Здесь вы видите, что добавление наносекундного числа на самом деле влияет на результат.

Проблемы с представлением времени в виде float хорошо известны, поэтому Python 3.7 ввел новую опцию. Каждая функция измерения времени теперь имеет соответствующую функцию _ns, которая возвращает число наносекунд в виде int вместо числа секунд в виде float. Например, time() теперь имеет наносекундный аналог time_ns():

Целые числа в Python не ограничены, поэтому это позволяет time_ns() давать разрешение наносекунды на всю вечность. Аналогично, perf_counter_ns() — это наносекундный вариант perf_counter():

Поскольку perf_counter() уже обеспечивает наносекундное разрешение, у использования perf_counter() меньше преимуществ.

Примечание: perf_counter_ns() доступен только в Python 3.7 и более поздних версиях. В этом уроке вы использовали perf_counter() в своем классе таймера. Таким образом, таймер можно использовать и в более старых версиях Python.

Для получения дополнительной информации о функциях _ns в time ознакомьтесь с новыми классными функциями в Python 3.7.

Есть две функции во time, которые не измеряют время, проведенное во сне (sleeping). Это process_time() и thread_time(), которые полезны в некоторых настройках. Однако для таймера обычно требуется измерить полное затраченное время. Последняя функция в приведенном выше monotonic(). Название намекает на то, что эта функция является монотонным таймером, который является таймером Python, который никогда не может двигаться назад.

Все эти функции монотонны, за исключением time(), который может вернуться назад, если системное время отрегулировано. В некоторых системах monotonic() — это та же функция, что и perf_counter(), и вы можете использовать их взаимозаменяемо. Однако это не всегда так. Вы можете использовать time.get_clock_info() для получения дополнительной информации о функции таймера Python. Используя Python 3.7 В Linux я получаю следующую информацию:

Результаты могут быть разными в вашей системе.

PIP 418 описывает некоторые обоснования введения этих функций. Она включает в себя следующие краткие описания:

  • time.monotonic(): тайм-аут и планирование, на которые не влияют обновления системных часов
  • time.perf_counter(): бенчмаркинг, самые точные часы за короткий период
  • time.process_time(): профилирование, процессорное время процесса

Как вы можете видеть, обычно это лучший выбор для вас, чтобы использовать perf_counter() для вашего таймера Python.

Оценка времени работы со временем timeit

Допустим, нужно выжать из кода последний бит производительности и задаетесь вопросом о наиболее эффективном способе преобразования списка в множество. Вы хотите сравнить, использование set() и литерал множества . Для этого вы можете использовать свой таймер Python:

>> from timer import Timer >>> numbers = [7, 6, 1, 4, 1, 8, 0, 6] >>> with Timer(text=»<:.8f>«): . set(numbers) . <0, 1, 4, 6, 7, 8>0.00007373 >>> with Timer(text=»<:.8f>«): . <*numbers>. <0, 1, 4, 6, 7, 8>0.00006204 —>

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

Лучше всего воспользоваться стандартной библиотекой. Она предназначен именно для измерения времени выполнения небольших фрагментов кода. Для этого импортируем и вызовем timeit.timeit() из Python как обычную функцию в интерфейсе командной строки. Вы можете рассчитать эти два варианта следующим образом:

timeit он автоматически вызывает ваш код много раз, чтобы усреднить шумные измерения. Результаты timeit подтверждают, что литерал set работает быстрее, чем set().

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

Наконец, интерактивная оболочка IPython и Jupyter notebook имеют дополнительную поддержку этой функции с помощью команды %timeit magic:

Опять же, измерения показывают, что использование литерала множества происходит быстрее.

Поиск узких мест в коде с помощью профилирования

timeit отлично подходит для бенчмаркинга конкретного фрагмента кода. Однако было бы очень громоздко использовать его для проверки всех частей вашей программы и определения того, какие разделы занимают больше всего времени. Вместо этого можно использовать профилировщик.

cProfile — это профилировщик, к которому вы можете получить доступ в любое время из стандартной библиотеки. Вы можете использовать его несколькими способами, хотя обычно проще всего использовать его в качестве инструмента командной строки:

Эта команда выполняет series_number.py с включенным профилированием. Вы сохраняете выходные данные из cProfile в series_number.prof как указано в параметре -o . Выходные данные находятся в двоичном формате, который нуждается в специальной программе, чтобы понять его. Опять же, Python содержит эту опцию прямо в стандартной библиотеке! Запуск модуля pstats на вашем компьютере с .prof файлом, открывает интерактивный браузер статистики профиля:

Чтобы использовать pstats, вы вводите команды в командной строке. Здесь вы можете увидеть интегрированную справочную систему. Обычно используются команды sort и stats. Чтобы получить более очищенный вывод, может быть полезна strip:

Этот вывод показывает, что общее время выполнения составило 0.002 секунды. В нем также перечислены десять функций, на которые ваш код потратил большую часть своего времени. Здесь вы отсортированы по кумулятивному времени (cumtime), что означает, что ваш код считает время, когда данная функция вызвала другую функцию.

Вы можете видеть, что ваш код проводит практически все свое время внутри модуля series_number, и в частности, внутри fibo(). Хотя это может быть полезным подтверждением того, что вы уже знаете, часто гораздо интереснее узнать, где ваш код на самом деле проводит время.

Столбец общее время (tottime) показывает, сколько времени ваш код провел внутри функции, исключая время в подфункциях. Вы можете видеть, что ни одна из вышеперечисленных функций на самом деле не тратит на это никакого времени. Чтобы найти, где код провел большую часть своего времени, выполните другую команду sort:

Теперь видно, что series_number.py фактически большую часть своего времени проводит работая с модулем fibo.py. Последнее является одной из зависимостей скрипта, который используется для вычисления.

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

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

К счастью, таймер вызывает только минимальные накладные расходы. Используйте quit, чтобы выйти из браузера pstats, когда вы закончите исследование.

Для получения более мощного интерфейса для анализа данных профиля, запустите программу KCacheGrind. Он использует свой собственный формат данных, но вы можете конвертировать данные из профиля с помощью pyprof2calltree:

Эта команда преобразует series_number.prof, затем открывая программу для анализа данных.

Последний вариант, который вы увидите здесь для замеров вашего кода — это line_profiler.cProfile может сказать вам, какие функции ваш код тратит больше всего времени, но он не даст вам понять, какие строки внутри этой функции являются самыми медленными. Вот где line_profiler может вам помочь.

Примечание: Вы также можете профилировать потребление памяти вашего кода. Это выходит за рамки данного руководства. Однако вы можете взглянуть на memory-profiler , если вам нужно контролировать потребление памяти вашими программами.

Обратите внимание, что line_profiler требует времени и добавляет изрядную часть накладных расходов к вашей среде выполнения. Более стандартный рабочий процесс заключается в том, чтобы сначала использовать cProfile для определения того, какие функции нужно просмотреть, а затем запустить line_profiler для этих функций. line_profiler не является частью стандартной библиотеки, поэтому вы должны сначала следовать инструкциям по установке, чтобы настроить его.

Перед запуском профилировщика необходимо указать ему, какие функции следует профилировать. Это выполняется, добавлением декоратора @profile в свой исходный код. Например, для профилирования Timer.stop() вы добавляете следующее в timer.py:

Обратите внимание, что вы нигде не импортируете profile. Вместо этого он автоматически добавляется в глобальное пространство имен при запуске профилировщика. Вы должны удалить строку, когда закончите профилирование. В противном случае вы получите NameError.

Затем запустим профилировщик, используя kernprof, который является частью пакета line_profiler:

Эта команда автоматически сохраняет данные профилировщика в файле series_number.py.lprof. Вы можете увидеть эти результаты, использу модуль line_profiler:

Во-первых, обратите внимание, что единица времени в этом отчете составляет микросекунда (1e-06 с). Обычно наиболее доступное число для просмотра — это %Time, которое показывает процент от общего времени, которое ваш код проводит внутри функции в каждой строке. В этом примере вы можете видеть, что ваш код тратит почти 70% времени на строке 47, которая является строкой, которая форматирует и печатает результат таймера.

Вывод

В этом руководстве вы увидели несколько разных подходов к добавлению таймера Python в свой код:

  • Вы использовали класс, чтобы сохранить состояние и добавить удобный интерфейс. Классы очень гибкие, и использование Timer напрямую дает вам полный контроль над тем, как и когда вызывать таймер.
  • Вы использовали менеджер контекста для добавления функций в блок кода и, при необходимости, для последующей очистки. Менеджеры контекста просты в использовании, а добавление с помощью Timer() может помочь вам более четко различать код визуально.
  • Вы использовали декоратор, чтобы добавить поведение к функции. Декораторы лаконичны и убедительны, а использование @Timer() — это быстрый способ отслеживать время выполнения вашего кода.

Вы также поняли, почему при тестировании кода предпочитаете time.perf_counter(), а не time.time(), а также какие другие альтернативы полезны при оптимизации кода.

Теперь вы можете добавить функции Timer Python в свой собственный код! Отслеживание скорости выполнения вашей программы в журналах поможет вам отслеживать ваши сценарии.

Источник

Читайте также:  Чем измерить 400 градусов