Искусство вычисления прошедшего времени в Ruby
Привет, коллеги-разработчики! Вы когда-нибудь задумывались о том, как вы считаете прошедшее время в Ruby? Если вы чем-то похожи на меня, вы, вероятно, не раз следовали этому шаблону:
starting = Time.now # Some time-consuming operation ending = Time.now elapsed = ending - starting elapsed # => 10.822178
Но держись! Здесь есть подвох, и все дело в том, как движется время. Приготовьтесь, потому что мы собираемся погрузиться в мир времени в Ruby и выясним, почему приведенный выше фрагмент не является идеальным способом измерения прошедшего времени.
Иллюзия линейного времени
Во-первых, давайте разберемся, что Time.now
делает в Ruby. В зависимости от настроек вашей операционной системы, Ruby Time.now
предназначен для использования функций Linux, таких как gettimeofday
или clock_gettime
из time.h
.
gettimeofday
возвращает количество секунд и микросекунд с начала эпохи, эффективно возвращая время стены в Linux. Однако здесь есть подвох:
На время, возвращаемое функцией
gettimeofday()
, влияют прерывистые скачки системного времени, в том числе ручная корректировка и автоматическое согласование системных часов.
Например, если ваш сервер использует сетевой протокол времени (NTP), эталонное время в идеале должно оставаться одинаковым во всем мире. Но даже NTP не гарантирует безупречной синхронизации.
В мире вычислений хронометраж — несовершенная наука, и физические условия процессора, такие как температура, давление воздуха и магнитные поля, влияют на эту точность. В результате вашей ОС, возможно, придется время от времени корректировать системное время. Примечательно, что эта корректировка может быть даже обратной, что приводит к сбоям в расчете прошедшего времени на основе времени стены.
Более того, некоторые ЦП с трудом справляются с дополнительными секундами, что приводит к аномалиям настенных часов.
Подводя итог, можно сказать, что использование системных часов для расчета прошедшего времени может привести к ошибкам или, в худшем случае, к сбоям в работе системы.
Правильный подход к измерению прошедшего времени
Итак, как мы можем ориентироваться в этом море переменных и точно вычислять прошедшее время? 🤔
Системы Posix имеют решение в виде монотонных часов. Этот таймер запускается с событием, и на него не влияют проблемы смещения времени. Он возвращает время, прошедшее с момента этого события, каждый раз, когда вы его вызываете.
К счастью, начиная с Ruby 2.1+ (MRI 2.1+ и JRuby 9.0.0.0+), в нашем распоряжении появился новый метод: Process.clock_gettime
. Это позволяет нам получить доступ к текущим значениям различных типов часов, включая монотонные часы:
t = Process.clock_gettime(Process::CLOCK_MONOTONIC) # => 2810266.714992 t / (24 * 60 * 60.0) # time / days # => 32.52623512722222
Здесь t
представляет время в секундах с момента загрузки системы.
Теперь мы можем использовать это для более надежного измерения прошедшего времени:
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC) # time consuming operation ending = Process.clock_gettime(Process::CLOCK_MONOTONIC) elapsed = ending - starting elapsed # => 9.183449000120163 seconds
Подведение итогов
Если вы работали в экосистеме Ruby, вас может удивить, что многочисленные расчеты прошедшего времени неточны из-за этого недоразумения. Итак, у вас есть прекрасная возможность сделать шаг вперед, внести свой вклад в сообщество открытого исходного кода 💚💎 и исправить эти проблемы.
И помните об этом фундаментальном