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

Исходный код: 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() с 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 count и number executions. Необязательный аргумент 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-строку модуля). stmt и setup могут также содержать несколько утверждений, разделенных ; или новыми строками, если они не содержат многострочных строковых литералов. По умолчанию оператор будет выполняться в пространстве имен timeit; этим поведением можно управлять, передавая пространство имен в globals.

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

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

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

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

timeit(number=1000000)

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

Примечание

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

сколько раз выполнить „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

В выводе есть три поля. Счетчик циклов, который показывает, сколько раз тело оператора было запущено за одно повторение цикла синхронизации. Количество повторений («лучший из 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, что приведет к тому, что код будет выполняться в вашем текущем глобальном пространстве имен. Это может быть удобнее, чем отдельно указывать imports:

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