Как использовать модуль timeit
Как мне использовать timeit
для сравнения производительности моих собственных функций, таких как "insertion_sort
" и "tim_sort
"?
Переведено автоматически
Ответ 1
Если вы хотите использовать timeit
в интерактивном сеансе Python, есть два удобных варианта:
Используйте оболочку IPython. Она имеет удобную
%timeit
специальную функцию:In [1]: def f(x):
...: return x*x
...:
In [2]: %timeit for x in range(100): f(x)
100000 loops, best of 3: 20.3 us per loopВ стандартном интерпретаторе Python вы можете получить доступ к функциям и другим именам, которые вы определили ранее во время интерактивного сеанса, импортировав их из
__main__
в инструкции setup:>>> def f(x):
... return x * x
...
>>> import timeit
>>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f",
number=100000)
[2.0640320777893066, 2.0876040458679199, 2.0520210266113281]
Ответ 2
Способ timeit
работы заключается в том, чтобы запустить установочный код один раз, а затем выполнить повторные вызовы серии инструкций. Итак, если вы хотите протестировать сортировку, требуется некоторая осторожность, чтобы один проход при сортировке на месте не повлиял на следующий проход с уже отсортированными данными (что, конечно, сделало бы Timsort действительно блестящим, потому что он работает лучше всего, когда данные уже частично упорядочены).
Вот пример того, как настроить тест для сортировки:
>>> import timeit
>>> setup = '''
import random
random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''
>>> print(min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000)))
0.04485079200821929
Обратите внимание, что серия инструкций создает новую копию несортированных данных на каждом проходе.
Также обратите внимание на методику синхронизации, заключающуюся в запуске набора измерений семь раз и сохранении только наилучшего времени — это действительно может помочь уменьшить искажения измерений из-за других процессов, запущенных в вашей системе.
Ответ 3
Я открою вам секрет: лучший способ использовать timeit
- это командная строка.
В командной строке timeit
выполняет надлежащий статистический анализ: сообщает вам, сколько времени занял самый короткий запуск. Это хорошо, потому что все ошибки во времени положительные. Таким образом, наименьшее время содержит наименьшую ошибку. Невозможно получить отрицательную ошибку, потому что компьютер никогда не сможет вычислять быстрее, чем он может вычислять!
Итак, интерфейс командной строки:
%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop
Это довольно просто, не так ли?
Вы можете настроить:
%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop
что тоже полезно!
Если вам нужно несколько строк, вы можете либо использовать автоматическое продолжение оболочки, либо использовать отдельные аргументы:
%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop
Это дает настройку
x = range(1000)
y = range(100)
и времена
sum(x)
min(y)
Если вы хотите иметь более длинные скрипты, у вас может возникнуть соблазн перейти к timeit
внутри скрипта Python. Я предлагаю избегать этого, потому что анализ и синхронизация просто лучше выполняются в командной строке. Вместо этого я обычно создаю сценарии оболочки:
SETUP="
... # lots of stuff
"
echo Minmod arr1
python -m timeit -s "$SETUP" "Minmod(arr1)"
echo pure_minmod arr1
python -m timeit -s "$SETUP" "pure_minmod(arr1)"
echo better_minmod arr1
python -m timeit -s "$SETUP" "better_minmod(arr1)"
... etc
Это может занять немного больше времени из-за нескольких инициализаций, но обычно это не имеет большого значения.
Но что, если вы хотите использовать timeit
внутри своего модуля?
Ну, простой способ - это сделать:
def function(...):
...
timeit.Timer(function).timeit(number=NUMBER)
и это дает вам совокупное (не минимальное!) время для запуска такое количество раз.
Чтобы получить хороший анализ, используйте .repeat
и используйте минимум:
min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))
Обычно вам следует комбинировать это с functools.partial
вместо lambda: ...
, чтобы снизить накладные расходы. Таким образом, у вас может получиться что-то вроде:
from functools import partial
def to_time(items):
...
test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)
# Divide by the number of repeats
time_taken = min(times) / 1000
Вы также можете сделать:
timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)
который дал бы вам что-то более близкое к интерфейсу из командной строки, но в гораздо менее крутой манере. "from __main__ import ..."
позволяет вам использовать код из вашего основного модуля внутри искусственной среды, созданной timeit
.
Стоит отметить, что это удобная оболочка для Timer(...).timeit(...)
и поэтому она не особенно хороша с синхронизацией. Лично я предпочитаю использовать Timer(...).repeat(...)
то, что я показал выше.
Предупреждения
Есть несколько предостережений с timeit
, которые выполняются везде.
Накладные расходы не учитываются. Допустим, вам нужно время
x += 1
, чтобы узнать, сколько времени занимает сложение:>>> python -m timeit -s "x = 0" "x += 1"
10000000 loops, best of 3: 0.0476 usec per loopНу, это не 0,0476 мкс. Вы только знаете, что это меньше этого значения. Все ошибки положительные.
Итак, попробуйте найти чистые накладные расходы:
>>> python -m timeit -s "x = 0" ""
100000000 loops, best of 3: 0.014 usec per loopЭто хорошие 30% накладных расходов только из-за синхронизации! Это может значительно исказить относительные тайминги. Но на самом деле вас волновали только тайминги добавления; тайминги поиска для
x
также должны быть включены в накладные расходы:>>> python -m timeit -s "x = 0" "x"
100000000 loops, best of 3: 0.0166 usec per loopРазница не намного больше, но она есть.
Методы изменения опасны.
>>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
10000000 loops, best of 3: 0.0436 usec per loopНо это совершенно неправильно!
x
это пустой список после первой итерации. Вам нужно будет повторно инициализировать:>>> python -m timeit "x = [0]*100000" "while x: x.pop()"
100 loops, best of 3: 9.79 msec per loopНо тогда у вас будет много накладных расходов. Учитывайте это отдельно.
>>> python -m timeit "x = [0]*100000"
1000 loops, best of 3: 261 usec per loopОбратите внимание, что вычитание накладных расходов здесь разумно только потому, что накладные расходы составляют небольшую долю времени.
Для вашего примера стоит отметить, что как сортировка по вставке, так и сортировка по времени имеют совершенно необычное поведение по времени для уже отсортированных списков. Это означает, что вам потребуется
random.shuffle
промежуточная сортировка, если вы хотите избежать нарушения ваших таймингов.
Ответ 4
Если вы хотите быстро сравнить два блока кода / функций, вы могли бы сделать:
import timeit
start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)