Содержание страницы
26.4. timeit – измерение времени выполнения небольших фрагментов кода¶timeit – Measure execution time of small code snippets
Исходный код: Lib/timeit.py
Этот модуль предоставляет простой способ измерения времени выполнения небольших фрагментов кода Python. Он имеет как интерфейс командной строки, так и вызываемый объект. Модуль избегает множества распространённых ловушек при измерении времени выполнения. См. также введение Тима Питерса к главе «Алгоритмы» в Python Cookbook, опубликованной O’Reilly.
26.4.1. Базовые примеры¶Basic Examples
Следующий пример показывает, как можно использовать интерфейс командной строки для сравнения трёх различных выражений:
$ python -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 40.3 usec per loop
$ python -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 33.4 usec per loop
$ python -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 25.2 usec per loop
Этого можно добиться из интерфейса Python с помощью:
>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.8187260627746582
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.7288308143615723
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.5858950614929199
Однако обратите внимание, что timeit автоматически определяет количество повторов только при использовании интерфейса командной строки. В разделе Примеры можно найти более сложные примеры.
26.4.2. Интерфейс Python¶Python Interface
Модуль определяет три вспомогательные функции и один открытый класс:
- timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000)¶
Создает экземпляр Timer с заданным оператором, кодом setup и функцией timer и запускает его метод timeit() с number выполнениями.
- timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=3, number=1000000)¶
Создает экземпляр Timer с заданным оператором, кодом setup и функцией timer и запускает его метод repeat() с заданным repeat и number выполнениями.
- timeit.default_timer()¶
Определяет таймер по умолчанию платформенно-зависимым образом. В Windows time.clock() имеет микросекундную точность, а time.time() – точность 1/60 секунды. В Unix time.clock() имеет точность 1/100 секунды, а time.time() гораздо точнее. На любой платформе default_timer() измеряет реальное время (wall clock time), а не процессорное. Это означает, что другие процессы, запущенные на том же компьютере, могут повлиять на замеры.
- class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)¶
Класс для измерения скорости выполнения небольших фрагментов кода.
Конструктор принимает измеряемый оператор, дополнительный оператор, используемый для настройки, и функцию таймера. Оба оператора по умолчанию равны 'pass'; функция таймера зависит от платформы (см. строку документации модуля). stmt и setup могут также содержать несколько операторов, разделенных ; или переводами строк, при условии, что они не содержат многострочных строковых литералов.
Для измерения времени выполнения первого оператора используйте метод timeit(). Метод repeat() – это удобная функция для вызова timeit() несколько раз и возврата списка результатов.
Параметры stmt и setup также могут принимать вызываемые объекты без аргументов. Это встраивает вызовы к ним в функцию таймера, которая затем выполняется с помощью timeit(). Обратите внимание, что накладные расходы на измерение времени в этом случае немного больше из-за дополнительных вызовов функций.
- timeit(number=1000000)¶
Измеряет время выполнения основного оператора number раз. Сначала выполняется оператор setup один раз, затем возвращается время, затраченное на выполнение основного оператора указанное количество раз, измеряемое в секундах в виде числа с плавающей запятой. Аргумент number – количество повторений цикла, по умолчанию миллион. Основной оператор, оператор setup и используемая функция таймера передаются конструктору.
Примечание
По умолчанию timeit() временно отключает сборку мусора во время измерения. Преимущество этого подхода в том, что он делает независимые измерения более сопоставимыми. Недостаток – сборка мусора может быть важной составляющей производительности измеряемой функции. Если это так, сборку мусора можно снова включить в качестве первой инструкции в строке setup. Например:
timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
- repeat(repeat=3, number=1000000)¶
Вызывает timeit() несколько раз.
Это удобная функция, которая многократно вызывает timeit(), возвращая список результатов. Первый аргумент указывает, сколько раз вызвать timeit(). Второй аргумент задает параметр number для timeit().
Примечание
Может возникнуть соблазн вычислить среднее и стандартное отклонение из вектора результатов и сообщить эти значения. Однако это не очень полезно. В типичном случае наименьшее значение дает нижнюю границу того, насколько быстро машина может выполнить заданный фрагмент кода; более высокие значения в векторе результатов обычно вызваны не изменчивостью скорости Python, а другими процессами, мешающими точности измерения времени. Таким образом, min() результата – вероятно, единственное число, которое представляет интерес. После этого следует рассмотреть весь вектор и применить здравый смысл, а не статистику.
- print_exc(file=None)¶
Вспомогательная функция для вывода трассировки из кода, время выполнения которого измеряется.
Типичное использование:
t = Timer(...) # за пределами try/except try: t.timeit(...) # или t.repeat(...) except: t.print_exc()
Преимущество по сравнению со стандартной трассировкой заключается в том, что исходные строки в скомпилированном шаблоне будут отображены. Необязательный аргумент file указывает, куда отправляется трассировка; по умолчанию это sys.stderr.
26.4.3. Интерфейс командной строки¶Command-Line Interface
При вызове как программы из командной строки используется следующая форма:
python -m timeit [-n N] [-r N] [-s S] [-t] [-c] [-h] [statement ...]
Где поддерживаются следующие параметры:
- -n N, --number=N¶
сколько раз выполнять 'statement'
- -r N, --repeat=N¶
сколько раз повторять таймер (по умолчанию 3)
- -s S, --setup=S¶
оператор, выполняемый один раз вначале (по умолчанию pass)
- -t, --time¶
использует time.time() (по умолчанию на всех платформах, кроме Windows)
- -c, --clock¶
использует time.clock() (по умолчанию в Windows)
- -v, --verbose¶
выводить необработанные результаты замера; для большей точности (больше знаков) повторите параметр
- -h, --help¶
вывести краткое сообщение об использовании и выйти
Многострочный оператор можно задать, указав каждую строку как отдельный аргумент оператора; строки с отступами возможны, если заключить аргумент в кавычки и использовать начальные пробелы. Несколько параметров -s обрабатываются аналогично.
Если -n не указан, подходящее количество циклов вычисляется путём последовательного перебора степеней 10, пока общее время не достигнет хотя бы 0,2 секунды.
Измерения с помощью default_timer() могут зависеть от других программ, работающих на том же компьютере, поэтому лучший способ при необходимости точных замеров – повторить измерение несколько раз и использовать лучшее время. Опция -r подходит для этого; значения по умолчанию – 3 повторения, скорее всего, достаточно в большинстве случаев. В Unix можно использовать time.clock() для измерения процессорного времени.
Примечание
Существует некоторая базовая накладная нагрузка, связанная с выполнением инструкции pass. Приведённый здесь код не пытается её скрыть, но о ней следует знать. Базовую накладную нагрузку можно измерить, вызвав программу без аргументов, и она может отличаться в разных версиях Python.
26.4.4. Примеры¶Examples
Можно указать инструкцию настройки, которая выполняется только один раз в начале:
$ python -m timeit -s 'text = "sample string"; char = "g"' 'char in text'
10000000 loops, best of 3: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"' 'text.find(char)'
1000000 loops, best of 3: 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.40193588800002544, 0.3960157959998014, 0.39594301399984033]
Следующие примеры показывают, как замерять выражения, содержащие несколько строк. Здесь мы сравниваем затраты на использование hasattr() и try/except для проверки отсутствующих и присутствующих атрибутов объектов:
$ python -m timeit 'try:' ' str.__bool__' 'except AttributeError:' ' pass'
100000 loops, best of 3: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
100000 loops, best of 3: 4.26 usec per loop
$ python -m timeit 'try:' ' int.__bool__' 'except AttributeError:' ' pass'
1000000 loops, best of 3: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 3: 2.23 usec per loop
>>> import timeit
>>> # атрибут отсутствует
>>> 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
>>>
>>> # атрибут присутствует
>>> 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"))