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

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

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

13 ответов


путь timeit works должен запустить код установки один раз, а затем сделать повторные вызовы серии операторов. Итак, если вы хотите протестировать сортировку, требуется некоторая осторожность, чтобы один проход на Сортировке на месте не повлиял на следующий проход с уже отсортированными данными (что, конечно, сделает 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. Надеюсь, это поможет : -)


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

  1. использовать оболочкой IPython shell. Он имеет удобный %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]
    

я открою вам секрет: лучший способ использовать 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)

что даст вам что-то ближе к интерфейс из командной строки, но в гораздо менее прохладно. The "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 µs. Вы только знаете, что это меньше чем. Все ошибки положительные.

    так что попробуйте найти чисто накладные расходы:

    >>> 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 между сортами, если вы хотите избежать вредительства синхронизации.


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

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)

Я нахожу самый простой способ использовать timeit из командной строки:

дано test.py:

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

запустите timeit следующим образом:

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

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

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)

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

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


timeit.timeit(stmt=foo, number=1234567)

Это прекрасно работает:

  python -m timeit -c "$(cat file_name.py)"

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

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

номер для запуска кода 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

по get: 0.54841166886888


просто передайте весь свой код в качестве аргумента 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))

встроенный модуль 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

пример использования интерпретатора 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                                                                                        

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

enter image description here Вот код для удобства его копирования

!/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")