timeit — Измерение времени выполнения небольших фрагментов кода
Исходный код: Lib/timeit.py
Этот модуль предоставляет простой способ измерения времени выполнения небольших фрагментов Python-кода. Он имеет как командную строку, так и вызываемую из Python версию. Он избегает ряда распространенных ловушек при измерении времени выполнения. Смотрите также введение Тима Петерса в главу «Алгоритмы» в книге «Python Cookbook», изданной издательством O’Reilly.
1. Базовые примеры
Следующий пример демонстрирует, как можно использовать командную строку для сравнения трех различных выражений:
$ python3 -m timeit '"-".join(str(n) for n in range(100))' 10000 loops, best of 3: 30.2 usec per loop $ python3 -m timeit '"-".join([str(n) for n in range(100)])' 10000 loops, best of 3: 27.5 usec per loop $ python3 -m timeit '"-".join(map(str, range(100)))' 10000 loops, best of 3: 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
Обратите внимание, что timeit автоматически определяет количество повторений только при использовании командной строки. В разделе «Примеры» вы найдете более сложные примеры.
2. Интерфейс 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=3, number=1000000, globals=None) -
Создаёт экземпляр
Timerс заданным оператором, кодом setup и функцией timer и выполняет его методrepeat()с заданным количеством повторений repeat и выполнений number. Дополнительный аргумент globals определяет пространство имён, в котором должен выполняться код.Изменено в версии 3.5: Добавлен необязательный параметр globals.
-
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()является удобным способом вызоваtimeit()несколько раз и возвращает список результатов.Время выполнения setup не учитывается в общем измерении времени выполнения.
Параметры stmt и setup также могут принимать объекты, вызываемые без аргументов. Это позволит встроить вызовы в функцию таймера, которая затем будет выполняться методом
timeit(). Обратите внимание, что накладные расходы на время немного больше в этом случае из-за дополнительных вызовов функций.Изменено в версии 3.5: Добавлен необязательный параметр globals.
-
timeit(number=1000000) -
Измерьте время выполнения оператора number раз. Оператор setup выполняется один раз, а затем возвращается время выполнения основного оператора несколько раз, измеренное в секундах как число с плавающей точкой. Аргумент задаёт количество повторений цикла, по умолчанию равное одному миллиону. Оператор, оператор настройки и функция таймера передаются в конструктор.
Примечание
По умолчанию,
timeit()временно отключает сбор мусора во время измерения. Преимущество этого подхода заключается в том, что независимые измерения становятся более сопоставимыми. Недостаток состоит в том, что GC может быть важной составляющей производительности измеряемой функции. Если это так, GC можно снова включить как первый оператор в строке 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(...) # outside the try/except try: t.timeit(...) # or t.repeat(...) except Exception: t.print_exc()Преимущество перед стандартной трассировкой стека заключается в том, что будут показаны строки из скомпилированного шаблона. Дополнительный аргумент file указывает, куда направляется трассировка; по умолчанию он равен
sys.stderr.
-
3. Командная строка
При вызове программы из командной строки используется следующий формат:
python -m timeit [-n N] [-r N] [-u U] [-s S] [-t] [-c] [-h] [statement ...]
Где поддерживаются следующие опции:
-
-n N, --number=N -
сколько раз выполнить оператор
-
-r N, --repeat=N -
сколько раз повторить таймер (по умолчанию 3)
-
-s S, --setup=S -
оператор, который будет выполнен один раз в начале (по умолчанию
pass)
-
-p, --process -
измерить время обработки, а не время wallclock, используя
time.process_time()вместоtime.perf_counter(), что является значением по умолчаниюДобавлена в версии 3.3.
-
-t, --time -
использовать
time.time()(устарело)
-
-u, --unit=U - задать единицу времени для вывода таймера; можно выбрать usec, msec или sec
Добавлена в версии 3.5.
-
-c, --clock -
использовать
time.clock()(устарело)
-
-v, --verbose -
вывести необработанные результаты измерения; повторите для большей точности
-
-h, --help -
вывести короткое сообщение об использовании и завершить программу
Многострочный оператор может быть задан указанием каждой строки как отдельного аргумента оператора; отступы возможны путем помещения аргумента в кавычки и использования ведущих пробелов. Несколько опций -s обрабатываются аналогично.
Если -n не задано, подходящее количество циклов вычисляется путем перебора последовательных степеней 10 до тех пор, пока общее время не составит не менее 0,2 секунд.
default_timer() измерения могут быть повлияны другими программами, работающими на том же компьютере, поэтому лучшим способом при необходимости точного измерения времени является повторение измерения несколько раз и использование наилучшего результата. Опция -r подходит для этого; по умолчанию 3 повторения, вероятно, достаточно в большинстве случаев. Вы можете использовать time.process_time() для измерения времени работы процессора.
Примечание
Существует определённая базовая накладная стоимость, связанная с выполнением инструкции pass. В данном примере код не пытается её скрыть, но вы должны быть осведомлены об этом. Базовую накладную стоимость можно измерить, вызвав программу без аргументов, и она может отличаться между версиями Python.
4. Примеры
Можно предоставить инструкцию подготовки, которая выполняется только один раз в начале:
$ 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 >>> # 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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.5/library/timeit.html