Вопрос-Ответ

How to use timeit module

Как использовать модуль timeit

Как мне использовать timeit для сравнения производительности моих собственных функций, таких как "insertion_sort" и "tim_sort"?

Переведено автоматически
Ответ 1

Если вы хотите использовать timeit в интерактивном сеансе Python, есть два удобных варианта:


  1. Используйте оболочку 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

  2. В стандартном интерпретаторе 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)
python