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


351

Я понимаю концепцию того, что timeitделает, но я не уверен, как реализовать это в моем коде.

Как я могу сравнить две функции, скажем, insertion_sortи tim_sort, с timeit?

Ответы:


266

Способ работы 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.334147930145

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

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

Это мои советы по правильному использованию времени. Надеюсь это поможет :-)


8
Да, он включает в себя копию списка (что очень быстро по сравнению с самой сортировкой). Если вы не копируете, первый проход сортирует список, а оставшиеся пропущенные не должны выполнять какую-либо работу. Если вы хотите узнать время только для сортировки, запустите приведенное выше с и без timsort(a)и возьмите разницу :-)
Raymond Hettinger

Я бы рекомендовал повторить 7 раз для каждой настройки, а затем усреднить; а не наоборот. Таким образом, если каждый всплеск из-за других процессов имеет хорошие шансы быть полностью проигнорированным, а не усредненным.
максимум

75
@max Используйте min (), а не среднее время. Это рекомендация от меня, от Тима Питерса и от Гвидо ван Россума. Самое быстрое время представляет собой лучшее, что алгоритм может выполнить, когда загружены кэши, а система не занята другими задачами. Все время шумное - самое быстрое время наименее шумное. Легко показать, что самые быстрые тайминги являются наиболее воспроизводимыми и, следовательно, наиболее полезными при синхронизации двух разных реализаций.
Рэймонд Хеттингер

4
Вы рассчитываете среднее (ну, общее, но это эквивалентно) для 1000 входных данных; затем повторите 7 раз и возьмите минимум . Вам необходимо усреднить более 1000 входных данных, потому что вы хотите усреднить (не в лучшем случае) сложность алгоритма. Вам нужен минимум именно по той причине, которую вы дали. Я думал, что могу улучшить ваш подход, выбрав один вход, запустив алгоритм 7 раз, взяв минимум; затем повторить его для 1000 различных входов и взять среднее. Чего я не понял, так это того, что вы .repeat(7,1000)уже делаете это (используя то же семя)! Таким образом, ваше решение идеально подходит для IMO.
максимум

5
Я могу только добавить, что то, как вы распределите свой бюджет на 7000 исполнений (например, .repeat(7, 1000)против и .repeat(2, 3500)против .repeat(35, 200), должно зависеть от того, как ошибка из-за загрузки системы сравнивается с ошибкой из-за изменчивости ввода. В крайнем случае, если ваша система всегда находится под большой нагрузкой, и вы видите длинный тонкий хвост слева от распределения времени выполнения (когда вы ловите его в редком состоянии простоя), вы можете даже .repeat(7000,1)оказаться более полезным, чем .repeat(7,1000)если бы вы не может бюджетировать более 7000 пробежек.
максимум

277

Если вы хотите использовать 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]
    

97
+1 за показ from __main__ import fтехники. Я не думаю, что это так широко известно, как должно быть. Это полезно в случаях, подобных этому, когда происходит вызов функции или метода. В других случаях (синхронизация ряда шагов) это менее полезно, потому что вводит издержки вызова функции.
Раймонд Хеттингер

15
Вы можете просто сделать%timeit f(x)
Qed

Примечание: настройка import f обеспечивает доступ к fa fast local read - что не совсем отражает глобальный вызов функции (короткой быстрой функции) в обычном нормальном коде. В Py3.5 + могут быть поставлены настоящие глобалы: «Изменено в версии 3.5: добавлен необязательный параметр глобалов.»; До глобалов модуля timeit, где это неизбежно (что не имеет особого смысла). Возможно, глобалы вызывающего кода ( sys._getframe(N).f_globals) должны были быть по умолчанию с самого начала.
KXR

140

Я открою вам секрет: лучший способ использовать 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перерывах между родами, если вы хотите избежать разрушения времени.


1
что означает usec? это микросекунды?
Хасан Икбал

2
@HasanIqbalAnik Да.
Veedrac

@StefanPochmann Потому что он не пытается пробовать несколько раз.
Veedrac


@Veedrac Рассматривая ваше утверждение о вычитании чисто временных затрат, timeitвыполняет passоператор, когда аргументы не заданы, что, конечно, занимает некоторое время. Если будут заданы какие-либо аргументы, passони не будут выполнены, поэтому вычитание некоторых 0.014usecs из каждого тайминга было бы некорректным.
Арне

99

Если вы хотите быстро сравнить два блока кода / функции, вы можете сделать:

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)

43

Я считаю, что самый простой способ использовать timeit из командной строки:

Учитывая test.py :

def InsertionSort(): ...
def TimSort(): ...

Запустите время так:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'


12
# Генерация целых чисел

def gen_prime(x):
    multiples = []
    results = []
    for i in range(2, x+1):
        if i not in multiples:
            results.append(i)
            for j in range(i*i, x+1, i):
                multiples.append(j)

    return results


import timeit

# Засекаем время

start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)

# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)


3

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

Аргумент настройки в основном настраивает словарь

Номер для запуска кода 1000000 раз. Не настройка, а stmt

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

Код в основном пытается получить значение c в словаре.

import timeit

print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

Вот мои результаты, ваши будут отличаться.

по индексу: 0.20900007452246427

по получению: 0.54841166886888


Какую версию Python вы используете?
Эдуардо

3

просто передайте весь код в качестве аргумента timeit:

import timeit

print(timeit.timeit(

"""   
limit = 10000
prime_list = [i for i in range(2, limit+1)]

for prime in prime_list:
    for elem in range(prime*2, max(prime_list)+1, prime):
        if elem in prime_list:
            prime_list.remove(elem)
"""   
, number=10))


0

Встроенный модуль timeit лучше всего работает из командной строки IPython.

Чтобы функции времени изнутри модуля:

from timeit import default_timer as timer
import sys

def timefunc(func, *args, **kwargs):
    """Time a function. 

    args:
        iterations=3

    Usage example:
        timeit(myfunc, 1, b=2)
    """
    try:
        iterations = kwargs.pop('iterations')
    except KeyError:
        iterations = 3
    elapsed = sys.maxsize
    for _ in range(iterations):
        start = timer()
        result = func(*args, **kwargs)
        elapsed = min(timer() - start, elapsed)
    print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
    return result

0

Пример использования интерпретатора Python REPL с функцией, принимающей параметры.

>>> import timeit                                                                                         

>>> def naive_func(x):                                                                                    
...     a = 0                                                                                             
...     for i in range(a):                                                                                
...         a += i                                                                                        
...     return a                                                                                          

>>> def wrapper(func, *args, **kwargs):                                                                   
...     def wrapper():                                                                                    
...         return func(*args, **kwargs)                                                                  
...     return wrapper                                                                                    

>>> wrapped = wrapper(naive_func, 1_000)                                                                  

>>> timeit.timeit(wrapped, number=1_000_000)                                                              
0.4458435332577161                                                                                        

0

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

введите описание изображения здесь Вот код для простоты его копирования

!/usr/local/bin/python3
import timeit

def fibonacci(n):
    """
    Returns the n-th Fibonacci number.
    """
    if(n == 0):
        result = 0
    elif(n == 1):
        result = 1
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    return result

if __name__ == '__main__':
    import timeit
    t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
    print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.