Spec-Zone.ru › Python 3.9

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

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

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

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

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

Создаёт экземпляр 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'; функция таймера зависит от платформы (см. строку документации модуля). stmt и setup также могут содержать несколько операторов, разделенных ; или символами новой строки, при условии, что они не содержат многострочные строковые литералы. Оператор по умолчанию будет выполняться в пространстве имён timeit; это поведение можно контролировать, передав пространство имён в globals.

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

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

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

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

timeit(number=1000000)

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

Примечание

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

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()))

© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/library/timeit.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API