timeit — Измерение времени выполнения небольших фрагментов кода

Исходный код: Lib/timeit.py


Модуль предоставляет простой способ замера времени выполнения небольших фрагментов Python кода. Реализует как интерфейс командной строки, так и вызываемый интерфейс. Позволяет избежать ряда распространенных ловушек для измерения времени выполнения. См. также введение Тима Петерса в главе «Алгоритмы» Сборника рецептов Python, опубликованного в 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() с number выполнений. Необязательный аргумент globals указывает пространство имён для выполнения кода.

Изменено в версии 3.5: Добавлен необязательный параметр globals.

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

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

Изменено в версии 3.5: Добавлен необязательный параметр globals.

Изменено в версии 3.7: По умолчанию значение repeat изменено с 3 на 5.

timeit.default_timer()

Таймер по умолчанию, который всегда time.perf_counter().

Изменено в версии 3.3: time.perf_counter() теперь является таймером по умолчанию.

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

Класс для замеров скорости выполнения небольших фрагментов кода.

Конструктор принимает оператор для синхронизации, дополнительный оператор используемый для настройки и функцию таймера. Оба оператора по умолчанию — 'pass'; функция таймера зависит от платформы (см. doc string модуля). stmt и setup могут также содержать несколько операторов, разделенных ; или новыми строками, если они не содержат многострочных строковых литералов. По умолчанию оператор будет выполняться в пространстве имён timeit; этим поведением можно управлять путём передачи пространства имён в globals.

Для измерения времени выполнения первого оператора используйте метод timeit(). Методы repeat() и autorange() являются удобными способами многократного вызова timeit().

Время выполнения setup исключается из общего времени выполнения.

Параметры stmt и setup могут также принимать объекты, вызываемые без аргументов. Вызовы будут встроены в функцию таймера, которая затем будет выполняться timeit(). Следует отметить, что служебные данные синхронизации в этом случае немного больше из-за дополнительных вызовов функций.

Изменено в версии 3.5: Добавлен необязательный параметр globals.

timeit(number=1000000)

Время number выполнений основного оператора. При этом оператор установки выполняется один раз, а затем возвращает время, необходимое для выполнения основного оператора, несколько раз, измеренное в секундах как float. Аргумент — это число раз в цикле, по умолчанию равное одному миллиону. Главный оператор, оператор установки и используемая функция таймера передаются конструктору.

Примечание

По умолчанию timeit() временно отключает сборщик мусора во время синхронизации. Преимущество этого подхода в том, что он делает независимые тайминги более сопоставимыми. Недостатком является то, что GC может быть важным компонентом эффективности измеряемой функции. Если это так, GC может быть повторно включен в качестве первого оператора в setup строке. Например:

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

Автоматически определяет количество вызовов timeit().

Это удобная функция, которая вызывает timeit() повторно, так что общее время >= 0.2 секунды, возвращая возможное (количество циклов, время, занятое для этого числа циклов). Она вызывает timeit() с возрастающими числами из последовательности 1, 2, 5, 10, 20, 50, … пока время не составит не менее 0.2 секунды.

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

Добавлено в версии 3.6.

repeat(repeat=5, number=1000000)

Вызов timeit() несколько раз.

Это удобная функция, которая вызывает timeit() повторно, возвращая список результатов. Первый аргумент указывает количество вызовов timeit(). Второй аргумент указывает number аргумент для timeit().

Примечание

Соблазнительно вычислить среднее и стандартное отклонение от вектора результата и сообщить об этом. Однако это не очень полезно. В типовом случае самое низкое значение даёт нижнюю границу для того, как быстро машина может запустить данный фрагмент код; более высокие значения вектора результата обычно вызваны не изменчивостью скорости Python’а, а другими процессами, мешающими точности синхронизации. Так что min() результата, наверное, единственное число, которое вас должно заинтересовать. После этого следует посмотреть на весь вектор и применить здравый смысл, а не статистику.

Изменено в версии 3.7: Значение repeat по умолчанию изменено с 3 на 5.

print_exc(file=None)

Помощник для печати трейсбэка из замеряемого кода.

Типичное использование:

t = Timer(...)       # вне try/except
try:
    t.timeit(...)    # или 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

сколько раз выполнить „statement“

-r N, --repeat=N

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

-s S, --setup=S

оператор для выполнения один раз изначально (по умолчанию pass)

-p, --process

измерить время процесса, а не время обхода, используя time.process_time() вместо time.perf_counter(), что является значением по умолчанию

Добавлено в версии 3.3.

-u, --unit=U

определяют единицу времени для вывода таймера; можно выбрать nsec, usec, msec или sec

Добавлено в версии 3.5.

-v, --verbose

печать необработанных результатов замеров; повтор для большей точности цифр

-h, --help

распечатать короткое сообщение об использовании и выйти из программы

Многострочный оператор может быть задан путём указания каждой строки в качестве отдельного аргумента оператора; строки с отступами можно заключить в кавычки и использовать начальные пробелы. Несколько опций -s обрабатываются одинаково.

Если -n не дано, подходящее количество циклов вычисляется путём попытки увеличения чисел из последовательности 1, 2, 5, 10, 20, 50,… пока общее время не составит по меньшей мере 0.2 секунды.

default_timer() измерения могут быть затронуты другими программами, работающими на той же машине, поэтому лучше всего, когда необходимо точное время, повторить время несколько раз и использовать лучшее время. Опция -r хороша для этого; по умолчанию 5 повторов, вероятно, достаточно в большинстве случаев. Для измерения времени ЦПУ можно использовать time.process_time().

Примечание

Есть определенные базовые накладные расходы, связанные с выполнением оператора pass. Здесь код не пытается скрыть, но вы должны знать об этом. Базовые издержки могут быть измерены путём вызова программы без аргументов, и они могут отличаться между 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
>>> 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, содержащий оператор import:

def test():
    """Глупая тестовая функция"""
    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()))