Я открою вам секрет: лучший способ использовать timeit
- это использовать в командной строке.
В командной строке timeit
выполняет надлежащий статистический анализ: он говорит вам, какдолго самый короткий пробег занял.Это хорошо, потому что ошибка all во времени положительна.Поэтому самое короткое время содержит наименьшую ошибку.Нет способа получить отрицательную ошибку, потому что компьютер не может вычислить быстрее, чем он может вычислить!
Итак, интерфейс командной строки:
%~> 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 мкс.Вы только знаете, что это меньше , чем это.Все ошибки положительны.
Так что попробуйте и найдите pure overhead:
>>> 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
Обратите внимание, что здесь разумно вычитать накладные расходы только потому, что накладные расходы составляют небольшую долю времени.Например, стоит отметить, что и Insertion Sort, и Tim Sort имеют совершенно необычные временные характеристики для уже отсортированных списков.Это означает, что вам потребуется random.shuffle
между сортами, если вы хотите избежать нарушения времени.