Як користуватися модулем 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

Зауважте, що серія тверджень робить нову копію несортованих даних при кожному пропуску.

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

Це мої поради щодо правильного використання timeit. Сподіваюсь, це допомагає :-)


8
Так, вона включає копію списку (що дуже швидко порівняно з самим сортом). Якщо ви не скопіюєте, перший прохід сортує список, а решта, що залишилася, не потребує жодної роботи. Якщо ви хочете дізнатись час лише для сортування, то запустіть вищезгадане із та без, timsort(a)і прийміть різницю :-)
Реймонд Хеттінгер

Я рекомендую повторити 7 разів для кожної установки, а потім середню; а не навпаки. Таким чином, якщо кожен спайк внаслідок інших процесів має шанси бути цілком ігнорованим, а не усередненим.
макс

75
@max Використовуйте min (), а не середній час. Це рекомендація від мене, від Тіма Пітерса та від Гвідо ван Россума. Найшвидший час являє собою найкращий алгоритм, який можна виконати, коли кеші завантажуються і система не зайнята іншими завданнями. Усі таймінги галасливі - найшвидший час - найменш галасливий. Неважко показати, що найшвидші терміни є найбільш відтворюваними і, отже, найбільш корисними при встановленні часу двох різних реалізацій.
Реймонд Хеттінгер

4
Ви обчислюєте середнє значення (ну, загальне, але воно еквівалентне) на 1000 входів; потім повторіть 7 разів і візьміть мінімум . Вам потрібно усереднення понад 1000 входів, оскільки ви хочете середню (не найкращий) складність алгоритму. Вам потрібен мінімум саме з тієї причини, яку ви дали. Я думав, що я можу вдосконалити ваш підхід, вибравши один вхід, запустивши алгоритм 7 разів, взявши мінімум; потім повторюється для 1000 різних входів і приймається середнє. Що я не усвідомлював, це те, що ваш .repeat(7,1000)уже робить це (використовуючи те саме насіння)! Тож ваше рішення - ідеальний ІМО.
макс

5
Я можу лише додати, що спосіб розподілу бюджету 7000 виконань (наприклад, .repeat(7, 1000)проти .repeat(2, 3500)vs .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__оператора настройки:

    >>> 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

Примітка: налаштування "імпортувати f" забезпечує доступ до швидкого локального зчитування - що не точно відображає глобальний виклик функції (короткої швидкої функції) у типовому нормальному коді. У 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 Так.
Ведрак

@StefanPochmann Оскільки він не намагається зробити вибірку кілька разів.
Ведрак


@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(): ...

запустити timeit так:

% 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

Запустивши це, ви можете побачити, що індекс набагато швидше, ніж отримати. Ви можете запустити його кілька разів, щоб побачити.

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

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

>>> 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.