Речь пойдет о функциях для измерения интервалов времени, расположенных
в модуле time
.
time()
и clock()
Очень долго их было ровно две: time.time()
и time.clock()
.
Использование предельно простое:
t1 = time.time()
# do tested stuff
print(time.time() - t1)
Получаем время выполнения кода в секундах.
Две функции нужны для того, чтобы усложнить жизнь программиста. Дело
в том, что на Windows точнее работает функция time.clock
, а на
прочих Unix/Linux — time.time
.
Дальнейшее касается Linux и (отчасти) других Posix систем. Все нововведения появятся в грядущем Python 3.3
clock_gettime(clk_id)
Вот уже много лет time.time
использует системный вызов
gettimeofday(2)
на тех ОС, где он доступен (Linux,
например). gettimeofday(2)
позволяет измерять время с точностью до
микросекунд в отличие от посекундного time(2)
.
Прогресс не стоит на месте, и появились функции c наносекундным
разрешением: time.clock_gettime
и time.clock_getres
являются
тонкими обёртками над одноимёнными Posix вызовами.
Зачем вообще нужны эти наносекунды и почему не хватает микросекундных интервалов?
Вот вполне жизненный пример: мы используем Redis для кеширования. Он работает на той же машине, где и основное приложение. Используются unix sockets и pipelining. В результате время обработки запросов сопоставимо с 1 мс. Если мы хотим знать, какую часть времени приложение тратит на общение с redis — нужно измерять интервалы с более высокой точностью.
clock_gettime
возвращает время, а clock_getres
показывает,
с какой точностью оперирует clock_gettime
.
Не вполне ясно, какой тип таймера выбирать. Вот список таймеров, имеющихся в ядре Linux 3:
-
CLOCK_REALTIME
— абсолютное системное время. -
CLOCK_MONOTONIC
— относительное время. -
CLOCK_MONOTONIC_RAW
— относительное время без коррекции NTP. -
CLOCK_PROCESS_CPUTIME_ID
— высокоточный таймер процесса. -
CLOCK_THREAD_CPUTIME_ID
— высокоточный таймер потока.
Во первых, возвращаемое время может быть абсолютным и относительным. Если по абсолютному времени можно узнать «который час», то относительное годится лишь для измерения интервалов. Относительное время отсчитывается от какой-то точки (для простоты пусть это будет момент включения компьютера), абсолютное традиционно начинается 1 января 1970 года (UNIX Epoch).
Поскольку Питон возвращает результат clock_gettime
как float
, то
наносекундная точность возможна лишь в течении 194 дней от UNIX Epoch
timestamp, микросекудная точность «заканчивается» за 272 года. 100
наносекндная точность исчерпывается за 17 лет. Вывод простой: для
измерения интервалов с точностью выше микросекунды пригодно только
относительное время.
Абсолютное время можно менять путём ручной или автоматизированной (службой NTP) подстройки компьютерных часов. Относительное при этом не изменяется.
На относительное время может оказывать влияние автоматическая
коррекция времени (NTP), которая за сутки сдвигает системное время на
доли секунды относительно аппаратных компьютерных часов. Когда
работаем с наносекундными интервалами — это имеет значение.
CLOCK_MONOTONIC_RAW
лишён такого недостатка.
И, наконец, есть таймеры, берущие значение из счётчиков процессора. Эти счётчики работают с очень высоким разрешением, да вот беда — в симметричных многопроцессорных системах (SMP) счётчики могут быть не связаны друг с другом. Процесс исполняется то на одном процессоре, то на другом, счётчики выдают несвязанные данные по которым нельзя ничего измерить. Широко распространённые однопроцессорные многоядерные системы этим недостатком не страдают. Простой совет: никогда не используете эти таймеры счётчиков процесса, не поняв до конца всех деталей. Для практических целей обычно достаточно менее экзотичных вариантов.
Перечисленные выше типы таймеров не очень полезны «обычному программисту»: совершенно непонятно, какой таймер выбрать прямо здесь и сейчас.
time.monotonic()
и time.wallclock()
Обычно желание простое: измерить интервал времени с максимально возможной точностью, причём способ не должен зависеть от установленной операционной системы и используемого «железа».
Вот тут и приходят на помощь функции time.monotonic()
и
time.wallclock()
. Они не требуют параметров, работают везде и
используют максимально точный из доступных таймеров. Для Windows это
clock()
, на Linux последовательно перебираются CLOCK_MONOTONIC_RAW
,
CLOCK_MONOTONIC
и CLOCK_REALTIME
. Кто первый отзовётся — тот и лучший.
Разница между функциями незначительная: time.wallclock()
попытается
перейти к таймеру абсолютного времени если не найдет лучший вариант (и
никогда не закончится исключением), time.monotonic()
выбросит
OSError
.
Итог
На самом деле, думаю, ни на одной современной системе до крайности не
дойдет и можно никогда не ощутить разницу на себе. И всё же
time.wallclock()
чуть-чуть больше похожа на то «стандартное
поведение», которое требуется от функции для измерения временных
интервалов. Если вам нужно оценить время выполнения чего-либо —
используйте её.
Очень интересный блог. Пишите чаще.
ОтветитьУдалитьЭто уж как пойдёт...
ОтветитьУдалить> Процесс исполняется то на одном процессоре, то на другом, счётчики выдают несвязанные данные по которым нельзя ничего измерить
ОтветитьУдалитьЕще не стоит забывать, что тактовая частота современных процессоров довольно часто меняется, особенно мобильных процессоров. Операционная система может изменять ее для всего кристалла или для отдельных ядер до 2х-5ти раз или вообще "усыплять" ядра если на них ничего не исполняется.
В линуксе эти все проблемы обходятся привязыванием процесса к определенному ядру или группе ядер через cgroups и временным запретом cpu power management. Эти действия очень желательны для любых тестов производительности.
Еще один повод не использовать CPUTIME, не разобравшись полностью во всех деталях.
ОтветитьУдалитьСпасибо.
Спасибо, классно.
ОтветитьУдалитьНа самом деле статья немного устарела. См. PEP-418: http://www.python.org/dev/peps/pep-0418/ (который, к слову, сейчас правится практически ежедневно).
ОтветитьУдалить