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

Источник: Lib/timeit.py


Этот модуль предоставляет простой способ тайминга небольших фрагментов кода Python. У него есть как Интерфейс командной строки, так и callable. Он позволяет избежать ряда распространенных ловушек при измерении времени выполнения. См. также введение Тима Питерса к главе «Алгоритмы» во втором издании Python Cookbook, опубликованном O’Reilly.

Основные примеры

В следующем примере показано, как Интерфейс командной строки можно использовать для сравнения трех разных выражений:

$ python -m timeit "'-'.join(str(n) for n in range(100))"
10000 loops, best of 5: 30.2 usec per loop
$ python -m timeit "'-'.join([str(n) for n in range(100)])"
10000 loops, best of 5: 27.5 usec per loop
$ python -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 и number выполнений. Необязательный аргумент 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 могут также содержать несколько утверждений, разделенных ; или новой строкой, если они не содержат многострочных строковых литералов. По умолчанию оператор будет выполняться в пространстве имен 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).

Added in version 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()

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

Интерфейс командной строки

При вызове программы из командной строки используется следующая форма:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-p] [-v] [-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(), которое используется по умолчанию

Added in version 3.3.

-u, --unit=U

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

Added in version 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()))