timeit — Measure execution time of small code snippets

Вихідний код: Lib/timeit.py


Цей модуль забезпечує простий спосіб вимірювання часу невеликих фрагментів коду Python. Він має як Інтерфейс командного рядка, так і callable. Це дозволяє уникнути ряду типових пасток для вимірювання часу виконання. Дивіться також вступ Тіма Пітерса до розділу «Алгоритми» у другому виданні Python Cookbook, опублікованому O’Reilly.

Основні приклади

У наступному прикладі показано, як Інтерфейс командного рядка можна використовувати для порівняння трьох різних виразів:

$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 5: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 5: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 5: 23.2 usec per loop

Цього можна досягти за допомогою Інтерфейс Python за допомогою:

>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237

Викликається також можна передати з Інтерфейс Python:

>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

Зауважте, однак, що timeit() автоматично визначатиме кількість повторень, лише якщо використовується інтерфейс командного рядка. У розділі Приклади ви можете знайти більш складні приклади.

Інтерфейс Python

Модуль визначає три зручні функції та публічний клас:

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

Створіть екземпляр Timer із заданим оператором, кодом setup і функцією timer і запустіть його метод timeit() з кількістю виконань. Необов’язковий аргумент globals визначає простір імен, у якому виконуватиметься код.

Змінено в версії 3.5: Додано необов’язковий параметр globals.

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

Створіть екземпляр Timer із заданим оператором, кодом setup і функцією timer і запустіть його метод repeat() із заданою кількістю repeat і кількістю виконань. Необов’язковий аргумент globals визначає простір імен, у якому виконуватиметься код.

Змінено в версії 3.5: Додано необов’язковий параметр globals.

Змінено в версії 3.7: Значення repeat за замовчуванням змінено з 3 на 5.

timeit.default_timer()

The default timer, which is always time.perf_counter().

Змінено в версії 3.3: time.perf_counter() тепер є таймером за умовчанням.

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

Клас швидкості виконання невеликих фрагментів коду.

Конструктор приймає інструкцію для визначення часу, додаткову інструкцію, яка використовується для налаштування, і функцію таймера. Обидва оператори за замовчуванням 'pass'; функція таймера залежить від платформи (дивіться рядок документа модуля). stmt і setup також можуть містити кілька операторів, розділених символами ; або символами нового рядка, за умови, що вони не містять багаторядкових рядкових літералів. Оператор за замовчуванням виконуватиметься в межах простору імен timeit; цією поведінкою можна керувати, передавши простір імен у globals.

Щоб виміряти час виконання першого оператора, використовуйте метод timeit(). Методи repeat() і autorange() є зручними для виклику timeit() кілька разів.

Час виконання setup виключається із загального часу виконання.

Параметри stmt і setup також можуть приймати об’єкти, які можна викликати без аргументів. Це вбудує виклики до них у функцію таймера, яка потім буде виконана timeit(). Зверніть увагу, що накладні витрати часу трохи більші в цьому випадку через додаткові виклики функцій.

Змінено в версії 3.5: Додано необов’язковий параметр globals.

timeit(number=1000000)

Time number executions of the main statement. This executes the setup statement once, and then returns the time it takes to execute the main statement a number of times, measured in seconds as a float. The argument is the number of times through the loop, defaulting to one million. The main statement, the setup statement and the timer function to be used are passed to the constructor.

Примітка

За замовчуванням timeit() тимчасово вимикає garbage collection протягом часу. Перевага цього підходу полягає в тому, що він робить незалежні таймінги більш порівнянними. Недоліком є те, що GC може бути важливим компонентом продуктивності вимірюваної функції. Якщо так, GC можна повторно ввімкнути як перший оператор у рядку setup. Наприклад:

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

Автоматично визначати, скільки разів викликати timeit().

This is a convenience function that calls timeit() repeatedly so that the total time >= 0.2 second, returning the eventual (number of loops, time taken for that number of loops). It calls timeit() with increasing numbers from the sequence 1, 2, 5, 10, 20, 50, … until the time taken is at least 0.2 second.

Якщо надано callback, а не None, його буде викликано після кожного випробування з двома аргументами: callback(number, time_taken).

Нове в версії 3.6.

repeat(repeat=5, number=1000000)

Викличте timeit() кілька разів.

Це зручна функція, яка багаторазово викликає timeit(), повертаючи список результатів. Перший аргумент визначає, скільки разів викликати timeit(). Другий аргумент визначає аргумент число для timeit().

Примітка

Спокусливо обчислити середнє та стандартне відхилення від вектора результату та повідомити про це. Однак це не дуже корисно. У типовому випадку найнижче значення дає нижню межу того, наскільки швидко ваша машина може запускати заданий фрагмент коду; більш високі значення у векторі результату зазвичай спричинені не мінливістю швидкості Python, а іншими процесами, які заважають вашій точності синхронізації. Тож min() результату, ймовірно, єдине число, яке вас має цікавити. Після цього вам слід переглянути весь вектор і застосувати здоровий глузд, а не статистику.

Змінено в версії 3.7: Значення repeat за замовчуванням змінено з 3 на 5.

print_exc(file=None)

Помічник для друку зворотного відстеження з часового коду.

Типове використання:

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

Перевага перед стандартним трасуванням полягає в тому, що вихідні рядки у скомпільованому шаблоні відображатимуться. Необов’язковий аргумент file вказує, куди надсилається відстеження; за замовчуванням sys.stderr.

Інтерфейс командного рядка

При виклику програми з командного рядка використовується така форма:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]

Де розуміються такі варіанти:

-n N, --number=N

скільки разів виконати „оператор“

-r N, --repeat=N

скільки разів повторити таймер (за замовчуванням 5)

-s S, --setup=S

оператор, який буде виконано один раз (за замовчуванням pass)

-p, --process

вимірювати час процесу, а не час настінного годинника, використовуючи time.process_time() замість time.perf_counter(), який є типовим

Нове в версії 3.3.

-u, --unit=U

specify a time unit for timer output; can select nsec, usec, msec, or sec

Нове в версії 3.5.

-v, --verbose

друк необроблених результатів синхронізації; повторіть для більшої точності цифр

-h, --help

надрукувати коротке повідомлення про використання та вийти

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

Якщо -n не вказано, відповідна кількість циклів обчислюється шляхом спроб збільшення чисел із послідовності 1, 2, 5, 10, 20, 50, … доки загальний час не становитиме принаймні 0,2 секунди .

default_timer() measurements can be affected by other programs running on the same machine, so the best thing to do when accurate timing is necessary is to repeat the timing a few times and use the best time. The -r option is good for this; the default of 5 repetitions is probably enough in most cases. You can use time.process_time() to measure CPU time.

Примітка

Існує певна базова накладна вартість, пов’язана з виконанням оператора проходу. Код тут не намагається це приховати, але ви повинні про це знати. Базові накладні витрати можна виміряти, викликавши програму без аргументів, і вони можуть відрізнятися в різних версіях Python.

Приклади

Можна надати оператор налаштування, який виконується лише один раз на початку:

$ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
1000000 loops, best of 5: 0.342 usec per loop

У вихідних даних є три поля. Кількість циклів, яка повідомляє вам, скільки разів було виконано тіло оператора за повторення циклу синхронізації. Кількість повторів («найкраще з 5»), яка повідомляє вам, скільки разів цикл синхронізації повторювався, і, нарешті, час, який у середньому витрачено на тіло оператора протягом найкращого повторення циклу синхронізації. Тобто час найшвидшого повторення, поділений на кількість циклів.

>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203

Те саме можна зробити за допомогою класу Timer та його методів:

>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

У наведених нижче прикладах показано, як визначити час для виразів, які містять кілька рядків. Тут ми порівнюємо вартість використання hasattr() і try/except для перевірки відсутніх і присутніх атрибутів об’єкта:

$ python -m timeit 'try:' '  str.__bool__' 'except AttributeError:' '  pass'
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 5: 2.23 usec per loop
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
...     str.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
...     int.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603

Щоб надати модулю timeit доступ до визначених вами функцій, ви можете передати параметр setup, який містить оператор імпорту:

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

Іншим варіантом є передача globals() у параметр globals, що спричинить виконання коду у вашому поточному глобальному просторі імен. Це може бути зручніше, ніж індивідуальне визначення імпорту:

def f(x):
    return x**2
def g(x):
    return x**4
def h(x):
    return x**8

import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))