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

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


Этот модуль предоставляет простой способ определения времени выполнения небольших фрагментов кода на Python. В нем есть как Интерфейс командной строки, так и callable. Он позволяет избежать ряда распространенных ошибок при измерении времени выполнения. Смотрите также введение Тима Питерса в главу «Алгоритмы» во втором издании «Кулинарной книги по 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 code и функцией 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 count и number executions. Необязательный аргумент globals указывает пространство имен, в котором будет выполняться код.

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

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

timeit.default_timer()

Таймер по умолчанию, который всегда используется как time.perf_counter(), возвращает значения с плавающей точкой в секундах. Альтернативный вариант, time.perf_counter_ns, возвращает целые наносекунды.

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

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

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

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

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

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

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

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

timeit(number=1000000)

Время количество выполнений инструкции main. При этом инструкция setup выполняется один раз, а затем возвращается время, необходимое для выполнения инструкции main несколько раз. Таймер по умолчанию возвращает количество секунд с плавающей точкой. Аргументом является количество повторений цикла, значение по умолчанию равно миллиону. Оператор main, оператор setup и функция timer, которые будут использоваться, передаются в конструктор.

Примечание

По умолчанию timeit() временно отключает garbage collection на время синхронизации. Преимущество этого подхода заключается в том, что он делает независимые тайминги более сопоставимыми. Недостатком является то, что сбор данных может быть важным компонентом производительности измеряемой функции. Если это так, 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 секунды.

Если задан обратный вызов, а не 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(...)       # 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

укажите единицу времени для вывода по таймеру; можно выбрать 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.

Примеры

Можно указать инструкцию setup, которая выполняется только один раз в начале:

$ 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, который содержит инструкцию import:

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()))
Вернуться на верх