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

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

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


person Neemaximo    schedule 22.11.2011    source источник


Ответы (13)


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

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

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

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

person Raymond Hettinger    schedule 22.11.2011
comment
Да, он включает копию списка (что очень быстро по сравнению с самой сортировкой). Если вы не копируете, первый проход сортирует список, и оставшиеся пройденные не должны выполнять никакой работы. Если вы хотите узнать время только для сортировки, запустите описанное выше с timsort(a) и без него и обратите внимание на разницу :-) - person Raymond Hettinger; 07.02.2012
comment
Я бы рекомендовал повторить 7 раз для каждой настройки, а затем усреднить; а не наоборот. Таким образом, если каждый всплеск, вызванный другими процессами, скорее всего, будет полностью проигнорирован, а не усреднен. - person max; 03.04.2012
comment
@max Используйте min (), а не среднее время. Это рекомендация от меня, Тима Петерса и Гвидо ван Россума. Самое быстрое время представляет собой лучшее, что может выполнить алгоритм, когда кеши загружены, а система не занята другими задачами. Все тайминги шумные - самое быстрое время наименее шумное. Легко показать, что самые быстрые тайминги являются наиболее воспроизводимыми и, следовательно, наиболее полезными при хронометрировании двух разных реализаций. - person Raymond Hettinger; 03.04.2012
comment
Вы вычисляете среднее (ну, всего, но это эквивалент) для 1000 входов; затем повторите 7 раз и возьмите минимум. Вам нужно усреднение по 1000 входам, потому что вам нужна средняя (не лучшая) сложность алгоритма. Вам нужен минимум именно по той причине, которую вы указали. Я думал, что смогу улучшить ваш подход, выбрав один вход, запустив алгоритм 7 раз, взяв минимум; затем повторяя это для 1000 различных входов и взяв среднее значение. Чего я не понимал, так это того, что ваш .repeat(7,1000) уже делает это (используя то же семя)! Так что ваше решение идеально IMO. - person max; 04.04.2012
comment
Я могу только добавить, что то, как вы распределяете свой бюджет на 7000 выполнений (например, .repeat(7, 1000) против .repeat(2, 3500) против .repeat(35, 200), должно зависеть от того, как ошибка из-за загрузки системы сравнивается с ошибкой из-за изменчивости ввода. В крайнем случае, если ваша система всегда находится под большой нагрузкой и вы видите длинный тонкий хвост слева от распределения времени выполнения (когда вы ловите его в редком состоянии ожидания), вы можете даже найти .repeat(7000,1) более полезным, чем .repeat(7,1000) если вы не можете запланировать более 7000 пробежек. - person max; 04.04.2012
comment
Как насчет дублирования массива, уже находящегося в настройке, создания итератора it над ними, а затем времени 'a=next(it); timsort(a)'? - person Stefan Pochmann; 26.12.2017
comment
Зачем повторять тысячу казней 7 раз? Есть ли идея не запускать установку один раз за выполнение? В каком случае, безусловно, правильный способ сделать это - повторить одно выполнение 7000 раз, используя .repeat(7000, 1)? - person binaryfunt; 04.06.2021
comment
Некоторые уточнения: создание копии исходных параметров, определенных в setup на каждой итерации, необходимо только в том случае, если ваша функция изменяет эти параметры, например, сортировка на месте. E. g. для sorted() вам не понадобится копия. Конечно, кто-то хочет проверить производительность любой функции, с побочными эффектами или без них. Но если вы избегаете функций с побочными эффектами, можно безопасно исключить оператор копирования. Теперь, если вы хотите сравнить оба типа функций и получить действительно точные измерения, вы можете вычесть продолжительность механизма копирования из тестов, в которых вы его применили. - person examiner; 08.06.2021

Если вы хотите использовать 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__ в операторе установки:

    >>> 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]
    
person Sven Marnach    schedule 22.11.2011
comment
+1 за демонстрацию техники from __main__ import f. Я не думаю, что это так широко известно, как должно быть. Это полезно в таких случаях, когда выполняется синхронизация вызова функции или метода. В других случаях (определение времени для серии шагов) это менее полезно, поскольку приводит к накладным расходам на вызов функции. - person Raymond Hettinger; 22.11.2011
comment
Вы можете просто сделать %timeit f(x) - person qed; 23.12.2014
comment
Примечание: настройка import f делает доступ к f быстрым локальным чтением, что не совсем точно отражает вызов глобальной функции (короткой быстрой функции) в типичном нормальном коде. В Py3.5 + могут быть предоставлены настоящие глобальные переменные: Изменено в версии 3.5: добавлен необязательный параметр глобальных переменных .; Перед глобальными объектами модуля timeit, где это неизбежно (что не имеет особого смысла). Возможно, глобальные переменные вызывающего кода (sys._getframe(N).f_globals) должны были быть по умолчанию с самого начала. - person kxr; 06.05.2017

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

person Veedrac    schedule 08.06.2014
comment
что означает usec? это микросекунды? - person QuestionEverything; 07.06.2017
comment
@HasanIqbalAnik Да. - person Veedrac; 07.06.2017
comment
@StefanPochmann Потому что он не пытается выполнить выборку несколько раз. - person Veedrac; 19.12.2017
comment
Читателей этого ответа может также заинтересовать Использовать timeit Python из программы, но функционирует так же, как командная строка?. - person Graham; 03.01.2019
comment
@Veedrac Учитывая ваше утверждение о вычитании чистых накладных расходов по времени, timeit выполняет pass оператор, когда не указаны аргументы, что, конечно, занимает некоторое время. Если заданы какие-либо аргументы, pass не будет выполняться, поэтому вычитание нескольких 0.014 мксек из каждого времени будет неправильным. - person Arne; 22.03.2019
comment
@Arne pass почти свободна для исполнения; Дело в том, чтобы измерить тестовый аппарат. - person Veedrac; 22.03.2019
comment
Вы уверены, что то, что вы говорите, является устройством отсчета времени, не временем выполнения pass? - person Arne; 22.03.2019
comment
@Arne Довольно уверенно, да. - person Veedrac; 22.03.2019
comment
@Veedrac Вы правы, я только что протестировал python -m 'pass' 'pass', и он был таким же быстрым, как и одиночный. Упс, может, стоило подумать об этом раньше. - person Arne; 23.03.2019
comment
Для всех, кто заглянет, вы также можете time python script.py отследить время выполнения всего скрипта из командной строки. - person JonPizza; 27.08.2019

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

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)
person zzart    schedule 08.04.2015

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

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

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

запустить время вот так:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'
person unutbu    schedule 22.11.2011

для меня это самый быстрый способ:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)
person Rodrigo Laguna    schedule 17.01.2018

Это отлично работает:

  python -m timeit -c "$(cat file_name.py)"
person Ohad Rubin    schedule 21.07.2017
comment
Что было бы эквивалентом Windows? - person Shailen; 11.10.2017
comment
Как передать параметры, если они требуются скрипту? - person Juuso Ohtonen; 20.12.2018

просто передайте весь свой код в качестве аргумента 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))
person Sébastien Wieckowski    schedule 04.10.2017

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

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

Номер - запустить код 1000000 раз. Не установка, а stmt

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

Код в основном пытается получить значение 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

person Stryker    schedule 26.09.2016
comment
Какую версию Python вы используете? - person Eduardo; 03.03.2020

Встроенный модуль 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
person ChaimG    schedule 05.06.2017

Пример использования интерпретатора 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                                                                                        
person Vlad Bezden    schedule 19.06.2017
comment
SyntaxError: позиционный аргумент следует за аргументом ключевого слова. Запустил его как из REPL, так и из отдельного файла. - person Aaj Kaal; 18.12.2020
comment
@ VladBezden, timeit.timeit(lambda: naive_func(x), number=1_000_000) имеет значение? - person garej; 13.01.2021

Вы должны создать две функции, а затем запустить что-то похожее на это. Обратите внимание, вы хотите выбрать такое же количество запусков / запусков для сравнения яблока и яблока.
Это было протестировано в 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")
person grepit    schedule 12.09.2018

person    schedule
comment
Что такое gc.enable()? - person Robin Andrews; 14.06.2020
comment
Активация Garbage Collection, который обычно деактивируется во время этих временных прогонов. - person aronadaal; 09.07.2021