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

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

Использование python timeit

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

Рассмотрим более простой пример.

#!/usr/bin/python3 def hello(a): b = a * 2 print (b) if __name__==’__main__’: from timeit import Timer t = Timer(lambda: hello(10)) print (t.timeit(number=14))

Здесь функция hello с одним передаваемым ей аргументом — это тестируемый код.

В функции main определяется, что hello требуется выполнять передавая значение аргумента 10. Выполнение требуется 14 раз.

20
20
20
20
20
20
20
20
20
20
20
20
20
20
0.0001920969998536748

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

Как измерить время выполнения кода в python #shorts #short

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

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

Можно выполнять скрипты и через Linux утилиту time:

#!/usr/bin/python3 import os os.system(«time python3 hello.py»)

Можно вызывать не однократное исполнение, запуск в цикле for.

Для той же цели подойдет и модуль subprocess. Он позволяет получать информацию в более удобной для дальнейшей обработки форме.

Источник: server-gu.ru

Разработка чрезвычайно быстрых программ на Python

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

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

Измерение времени и профилирование

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

Ответы на вопросы 3: Замер времени исполнения программы.

Ниже представлен код программы, который я буду использовать в демонстрационных целях. Он взят из документации к Python. Этот код возводит e в степень x :

# slow_program.py from decimal import * def exp(x): getcontext().prec += 2 i, lasts, s, fact, num = 0, 0, 1, 1, 1 while s != lasts: lasts = s i += 1 fact *= i num *= x s += num / fact getcontext().prec -= 2 return +s exp(Decimal(150)) exp(Decimal(400)) exp(Decimal(3000))

Самый лёгкий способ «профилирования» кода

Для начала рассмотрим самый простой способ профилирования кода. Так сказать, «профилирование для ленивых». Он заключается в использовании команды Unix time :

~ $ time python3.8 slow_program.py real 0m11,058s user 0m11,050s sys 0m0,008s

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

Самый точный способ профилирования

На другом конце спектра методов профилирования кода лежит инструмент cProfile , который даёт программисту, надо признать, слишком много сведений:

~ $ python3.8 -m cProfile -s time slow_program.py 1297 function calls (1272 primitive calls) in 11.081 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 3 11.079 3.693 11.079 3.693 slow_program.py:4(exp) 1 0.000 0.000 0.002 0.002 4/1 0.000 0.000 11.081 11.081 6 0.000 0.000 0.000 0.000 6 0.000 0.000 0.000 0.000 abc.py:132(__new__) 23 0.000 0.000 0.000 0.000 _weakrefset.py:36(__init__) 245 0.000 0.000 0.000 0.000 2 0.000 0.000 0.000 0.000 10 0.000 0.000 0.000 0.000 :1233(find_spec) 8/4 0.000 0.000 0.000 0.000 abc.py:196(__subclasscheck__) 15 0.000 0.000 0.000 0.000 6 0.000 0.000 0.000 0.000 1 0.000 0.000 0.000 0.000 __init__.py:357(namedtuple) 48 0.000 0.000 0.000 0.000 :57(_path_join) 48 0.000 0.000 0.000 0.000 :59() 1 0.000 0.000 11.081 11.081 slow_program.py:1()

Тут мы запускаем исследуемый скрипт с использованием модуля cProfile и применяем аргумент time . В результате строки вывода упорядочены по внутреннему времени ( cumtime ). Это даёт нам очень много информации. На самом деле то, что показано выше, это лишь около 10% вывода cProfile .

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

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

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

Читайте также:
Как скинуть программу на хлебопечке кенвуд

Этот декоратор можно применить к функции, которую нужно исследовать:

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

~ $ python3.8 slow_program.py module function time __main__ .exp : 0.003267502994276583 __main__ .exp : 0.038535295985639095 __main__ .exp : 11.728486061969306

Тут стоит обратить внимание на то, какое именно время мы планируем измерять.

Соответствующий пакет предоставляет нам такие показатели, как time.perf_counter и time.process_time . Разница между ними заключается в том, что perf_counter возвращает абсолютное значение, в которое входит и то время, в течение которого процесс Python-программы не выполняется. Это значит, что на этот показатель может повлиять нагрузка на компьютер, создаваемая другими программами. Показатель process_time возвращает только пользовательское время (user time). В него не входит системное время (system time). Это даёт нам только сведения о времени выполнения нашего процесса.

Ускорение кода

А теперь переходим к самому интересному. Поработаем над ускорением программы. Я (по большей части) не собираюсь показывать тут всякие хаки, трюки и таинственные фрагменты кода, которые волшебным образом решают проблемы производительности. Я, в основном, хочу поговорить об общих идеях и стратегиях, которые, если ими пользоваться, могут очень сильно повлиять на производительность. В некоторых случаях речь идёт о 30% повышении скорости выполнения кода.

▍Используйте встроенные типы данных

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

▍Применяйте кэширование (мемоизацию) с помощью lru_cache

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

Вышеприведённая функция имитирует сложные вычисления, используя time.sleep . Когда её в первый раз вызывают с параметром 1 — она ждёт 2 секунды и возвращает результат только после этого. Когда же её снова вызывают с тем же параметром, оказывается, что результат её работы уже кэширован. Тело функции в такой ситуации не выполняется, а результат возвращается немедленно. Здесь можно найти примеры применения кэширования, более близкие к реальности.

▍Используйте локальные переменные

Применяя локальные переменные, мы учитываем скорость поиска переменной в каждой области видимости. Я говорю именно о «каждой области видимости», так как тут я имею в виду не только сопоставление скорости работы с локальными и глобальными переменными. На самом деле, разница в работе с переменными наблюдается даже, скажем, между локальными переменными в функции (самая высокая скорость), атрибутами уровня класса (например — self.name , это уже медленнее), и глобальными импортированными сущностями наподобие time.time (самый медленный из этих трёх механизмов).

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

# Пример #1 class FastClass: def do_stuff(self): temp = self.value # это ускорит цикл for i in range(10000): . # Выполняем тут некие операции с `temp` # Пример #2 import random def fast_function(): r = random.random for i in range(10000): print(r()) # здесь вызов `r()` быстрее, чем был бы вызов random.random()

▍Оборачивайте код в функции

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

def main(): . # Весь код, который раньше был глобальным main()

▍Не обращайтесь к атрибутам

Ещё один механизм, способный замедлить программу — это оператор точка ( . ), который используется для доступа к атрибутам объектов. Этот оператор вызывает выполнение процедуры поиска по словарю с использованием __getattribute__ , что создаёт дополнительную нагрузку на систему. Как ограничить влияние этой особенности Python на производительность?

# Медленно: import re def slow_func(): for i in range(10000): re.findall(regex, line) # Медленно! # Быстро: from re import findall def fast_func(): for i in range(10000): findall(regex, line) # Быстрее!

▍Остерегайтесь строк

Операции на строках могут сильно замедлить программу в том случае, если выполняются в циклах. В частности, речь идёт о форматировании строк с использованием %s и .format() . Можно ли их чем-то заменить?

Если взглянуть на недавний твит Раймонда Хеттингера, то можно понять, что единственный механизм, который надо использовать в подобных ситуациях — это f-строки. Это — самый читабельный, лаконичный и самый быстрый метод форматирования строк. Вот, в соответствии с тем твитом, список методов, которые можно использовать для работы со строками — от самого быстрого к самому медленному:

f’ ‘ # Быстро! s + ‘ ‘ + t ‘ ‘.join((s, t)) ‘%s %s’ % (s, t) ‘<> <>’.format(s, t) Template(‘$s $t’).substitute(s=s, t=t) # Медленно!

▍Знайте о том, что и генераторы могут работать быстро

Генераторы — это не те механизмы, которые, по своей природе, являются быстрыми. Дело в том, что они были созданы для выполнения «ленивых» вычислений, что экономит не время, а память.

Однако экономия памяти может привести к тому, что программы будут выполняться быстрее. Как это возможно? Дело в том, что при обработке большого набора данных без использования генераторов (итераторов) данные могут привести к переполнению L1-кэша процессора, что значительно замедлит операции по поиску значений в памяти.

Читайте также:
Какие программы есть для видеосвязи

Если речь идёт о производительности, очень важно стремиться к тому, чтобы процессор мог бы быстро обращаться к обрабатываемым им данным, чтобы они находились бы как можно ближе к нему. А это значит, что такие данные должны помещаться в процессорном кэше. Этот вопрос затрагивается в данном выступлении Раймонда Хеттингера.

Итоги

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

Уважаемые читатели! Как вы подходите к оптимизации производительности своего Python-кода?

Источник: habr.com

timeit — Измерьте время выполнения небольших фрагментов кода¶

Этот модуль предоставляет простой способ временной обработки небольших фрагментов кода Python. В нем есть как Интерфейс командной строки , так и callable . Он позволяет избежать ряда распространенных ловушек при измерении времени выполнения. См. также введение Тима Питерса к главе «Алгоритмы» во втором издании Python Cookbook, опубликованном O’Reilly.

Основные примеры¶

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

$ python3 -m timeit ‘»-«.join(str(n) for n in range(100))’ 10000 loops, best of 5: 30.2 usec per loop $ python3 -m timeit ‘»-«.join([str(n) for n in range(100)])’ 10000 loops, best of 5: 27.5 usec per loop $ python3 -m timeit ‘»-«.join(map(str, range(100)))’ 10000 loops, best of 5: 23.2 usec per loop

Этого можно достичь с помощью Интерфейс Python с:

>>> import timeit >>> timeit.timeit(‘»-«.join(str(n) for n in range(100))’, number=10000) 0.3018611848820001 >>> timeit.timeit(‘»-«.join([str(n) for n in range(100)])’, number=10000) 0.2727368790656328 >>> timeit.timeit(‘»-«.join(map(str, range(100)))’, number=10000) 0.23702679807320237

Вызываемый объект также может быть передан из Интерфейс Python :

>>> timeit.timeit(lambda: «-«.join(map(str, range(100))), number=10000) 0.19665591977536678

Обратите внимание, что timeit() автоматически определяет количество повторений только при использовании интерфейса командной строки. В разделе Примеры вы можете найти более сложные примеры.

Интерфейс Python¶

Модуль определяет три удобные функции и публичный класс:

timeit. timeit ( stmt=’pass’, setup=’pass’, timer= , number=1000000, globals=None ) ¶

Создайте экземпляр Timer с заданным оператором, кодом setup и функцией timer и запустите его метод timeit() с number выполнений. Необязательный аргумент globals задает пространство имен, в котором будет выполняться код.

Изменено в версии 3.5: Добавлен необязательный параметр globals.

timeit. repeat ( stmt=’pass’, setup=’pass’, timer= , repeat=5, number=1000000, globals=None ) ¶

Создает экземпляр Timer с заданным оператором, setup кодом и timer функцией и запускает его repeat() метод с заданными repeat count и number executions. Необязательный аргумент globals задает пространство имен, в котором будет выполняться код.

Изменено в версии 3.5: Добавлен необязательный параметр globals.

Изменено в версии 3.7: Значение по умолчанию repeat изменено с 3 на 5.

timeit. default_timer ( ) ¶

Таймер по умолчанию, который всегда time.perf_counter() .

Изменено в версии 3.3: time.perf_counter() теперь является таймером по умолчанию.

class timeit. Timer ( stmt=’pass’, setup=’pass’, timer= , globals=None ) ¶

Класс для определения скорости выполнения небольших фрагментов кода.

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

Время выполнения setup исключается из общего времени выполнения.

Параметры stmt и setup также могут принимать объекты, которые можно вызывать без аргументов. Это позволит встроить вызовы к ним в функцию таймера, которая затем будет выполняться timeit() . Обратите внимание, что временные затраты в этом случае немного больше из-за дополнительных вызовов функций.

Изменено в версии 3.5: Добавлен необязательный параметр globals.

timeit ( number = 1000000 ) ¶

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

По умолчанию timeit() временно отключает garbage collection на время тайминга. Преимущество этого подхода в том, что он делает независимые тайминги более сопоставимыми. Недостатком является то, что GC может быть важным компонентом производительности измеряемой функции. Если это так, то GC может быть повторно включен в качестве первого оператора в строке setup. Например:

timeit.Timer(‘for i in range(10): oct(i)’, ‘gc.enable()’).timeit()
autorange ( callback = None ) ¶

Автоматически определите, сколько раз нужно вызвать timeit() .

Это удобная функция, которая многократно вызывает timeit() так, что общее время >= 0,2 секунды, возвращая в итоге (количество циклов, время, затраченное на это количество циклов). Она вызывает timeit() с возрастающими числами из последовательности 1, 2, 5, 10, 20, 50, … до тех пор, пока затраченное время не станет не менее 0.2 секунды.

Если callback задан и не является None , он будет вызываться после каждого испытания с двумя аргументами: callback(number, time_taken) .

Добавлено в версии 3.6.

repeat ( repeat = 5 , number = 1000000 ) ¶

Вызовите timeit() несколько раз.

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

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

Читайте также:
Как поменять бонусную программу в втб

Изменено в версии 3.7: Значение по умолчанию repeat изменено с 3 на 5.

print_exc ( file = None ) ¶

Помощник для печати трассировки из кода с таймером.

t = Timer(. ) # outside the try/except try: t.timeit(. ) # or t.repeat(. ) except Exception: t.print_exc()

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

Интерфейс командной строки¶

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

python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement . ]

Где понятны следующие варианты:

сколько раз выполнить „statement“

сколько раз повторять таймер (по умолчанию 5)

-s S , —setup =S ¶

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

измерять время процесса, а не время стенных часов, используя time.process_time() вместо time.perf_counter() , которое используется по умолчанию

Добавлено в версии 3.3.

указать единицу времени для вывода таймера; можно выбрать nsec, usec, msec или sec

Добавлено в версии 3.5.

-v , —verbose ¶

вывести необработанные результаты хронометража; повторить для получения большего количества цифр точности

вывести короткое сообщение об использовании и выйти

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

Если -n не задано, подходящее количество циклов рассчитывается путем перебора возрастающих чисел из последовательности 1, 2, 5, 10, 20, 50, … до тех пор, пока общее время не будет не менее 0,2 секунды.

На измерения default_timer() могут влиять другие программы, запущенные на той же машине, поэтому при необходимости точного определения времени лучше всего повторить несколько раз и использовать лучшее время. Для этого хорошо подходит опция -r ; в большинстве случаев достаточно 5 повторений по умолчанию. Вы можете использовать time.process_time() для измерения времени процессора.

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

Примеры¶

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

$ python -m timeit -s ‘text = «sample string»; char = «g»‘ ‘char in text’ 5000000 loops, best of 5: 0.0877 usec per loop $ python -m timeit -s ‘text = «sample string»; char = «g»‘ ‘text.find(char)’ 1000000 loops, best of 5: 0.342 usec per loop

В выводе есть три поля. Счетчик циклов, который показывает, сколько раз тело оператора было запущено за одно повторение цикла синхронизации. Количество повторений («лучший из 5»), которое показывает, сколько раз был повторен цикл синхронизации, и, наконец, время, которое в среднем заняло тело оператора в лучшем повторении цикла синхронизации. То есть время самого быстрого повторения делится на количество циклов.

>>> import timeit >>> timeit.timeit(‘char in text’, setup=’text = «sample string»; char = «g»‘) 0.41440500499993504 >>> timeit.timeit(‘text.find(char)’, setup=’text = «sample string»; char = «g»‘) 1.7246671520006203

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

>>> import timeit >>> t = timeit.Timer(‘char in text’, setup=’text = «sample string»; char = «g»‘) >>> t.timeit() 0.3955516149999312 >>> t.repeat() [0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

В следующих примерах показано, как проверять по времени выражения, содержащие несколько строк. Здесь мы сравниваем стоимость использования hasattr() по сравнению с try / except для проверки на отсутствующие и присутствующие атрибуты объекта:

$ python -m timeit ‘try:’ ‘ str.__bool__’ ‘except AttributeError:’ ‘ pass’ 20000 loops, best of 5: 15.7 usec per loop $ python -m timeit ‘if hasattr(str, «__bool__»): pass’ 50000 loops, best of 5: 4.26 usec per loop $ python -m timeit ‘try:’ ‘ int.__bool__’ ‘except AttributeError:’ ‘ pass’ 200000 loops, best of 5: 1.43 usec per loop $ python -m timeit ‘if hasattr(int, «__bool__»): pass’ 100000 loops, best of 5: 2.23 usec per loop
>>> import timeit >>> # attribute is missing >>> s = «»» . try: . str.__bool__ . except AttributeError: . pass . «»» >>> timeit.timeit(stmt=s, number=100000) 0.9138244460009446 >>> s = «if hasattr(str, ‘__bool__’): pass» >>> timeit.timeit(stmt=s, number=100000) 0.5829014980008651 >>> >>> # attribute is present >>> s = «»» . try: . int.__bool__ . except AttributeError: . pass . «»» >>> timeit.timeit(stmt=s, number=100000) 0.04215312199994514 >>> s = «if hasattr(int, ‘__bool__’): pass» >>> timeit.timeit(stmt=s, number=100000) 0.08588060699912603

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

def test(): «»»Stupid test function»»» L = [i for i in range(100)] if __name__ == ‘__main__’: import timeit print(timeit.timeit(«test()», setup=»from __main__ import test»))

Другой вариант — передать globals() в параметр globals, что приведет к тому, что код будет выполняться в вашем текущем глобальном пространстве имен. Это может быть удобнее, чем отдельно указывать imports:

def f(x): return x**2 def g(x): return x**4 def h(x): return x**8 import timeit print(timeit.timeit(‘[func(42) for func in (f,g,h)]’, globals=globals()))

Источник: django.fun

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru