How to time an operation in milliseconds in Ruby? - ruby

I'm wishing to figure out how many milliseconds a particular function uses. So I looked high and low, but could not find a way to get the time in Ruby with millisecond precision.
How do you do this? In most programming languages its just something like
start = now.milliseconds
myfunction()
end = now.milliseconds
time = end - start

You can use ruby's Time class. For example:
t1 = Time.now
# processing...
t2 = Time.now
delta = t2 - t1 # in seconds
Now, delta is a float object and you can get as fine grain a result as the class will provide.

You can also use the built-in Benchmark.measure function:
require "benchmark"
puts(Benchmark.measure { sleep 0.5 })
Prints:
0.000000 0.000000 0.000000 ( 0.501134)

Using Time.now (which returns the wall-clock time) as base-lines has a couple of issues which can result in unexpected behavior. This is caused by the fact that the wallclock time is subject to changes like inserted leap-seconds or time slewing to adjust the local time to a reference time.
If there is e.g. a leap second inserted during measurement, it will be off by a second. Similarly, depending on local system conditions, you might have to deal with daylight-saving-times, quicker or slower running clocks, or the clock even jumping back in time, resulting in a negative duration, and many other issues.
A solution to this issue is to use a different time of clock: a monotonic clock. This type of clock has different properties than the wall clock.
It increments monitonically, i.e. never goes back and increases at a constant rate. With that, it does not represent the wall-clock (i.e. the time you read from a clock on your wall) but a timestamp you can compare with a later timestamp to get a difference.
In Ruby, you can use such a timestamp with Process.clock_gettime(Process::CLOCK_MONOTONIC) like follows:
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 63988.576809828
sleep 1.5 # do some work
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 63990.08359163
delta = t2 - t1
# => 1.5067818019961123
delta_in_milliseconds = delta * 1000
# => 1506.7818019961123
The Process.clock_gettime method returns a timestamp as a float with fractional seconds. The actual number returned has no defined meaning (that you should rely on). However, you can be sure that the next call will return a larger number and by comparing the values, you can get the real time difference.
These attributes make the method a prime candidate for measuring time differences without seeing your program fail in the least opportune times (e.g. at midnight at New Year's Eve when there is another leap-second inserted).
The Process::CLOCK_MONOTONIC constant used here is available on all modern Linux, BSD, and macOS systems as well as the Linux Subsystem for Windows. It is however not yet available for "raw" Windows systems. There, you can use the GetTickCount64 system call instead of Process.clock_gettime which also returns a timer value in millisecond granularity on Windows (>= Windows Vista, Windows Server 2008).
With Ruby, you can call this function like this:
require 'fiddle'
# Get a reference to the function once
GetTickCount64 = Fiddle::Function.new(
Fiddle.dlopen('kernel32.dll')['GetTickCount64'],
[],
-Fiddle::TYPE_LONG_LONG # unsigned long long
)
timestamp = GetTickCount64.call / 1000.0
# => 63988.576809828

You should take a look at the benchmark module to perform benchmarks. However, as a quick and dirty timing method you can use something like this:
def time
now = Time.now.to_f
yield
endd = Time.now.to_f
endd - now
end
Note the use of Time.now.to_f, which unlike to_i, won't truncate to seconds.

Also we can create simple function to log any block of code:
def log_time
start_at = Time.now
yield if block_given?
execution_time = (Time.now - start_at).round(2)
puts "Execution time: #{execution_time}s"
end
log_time { sleep(2.545) } # Execution time: 2.55s

Use Time.now.to_f

The absolute_time gem is a drop-in replacement for Benchmark, but uses native instructions to be far more accurate.

If you use
date = Time.now.to_i
You're obtaining time in seconds, that is far from accurate, specially if you are timing little chunks of code.

The use of Time.now.to_i return the second passed from 1970/01/01. Knowing this you can do
date1 = Time.now.to_f
date2 = Time.now.to_f
diff = date2 - date1
With this you will have difference in second magnitude. If you want it in milliseconds, just add to the code
diff = diff * 1000

I've a gem which can profile your ruby method (instance or class) - https://github.com/igorkasyanchuk/benchmark_methods.
No more code like this:
t = Time.now
user.calculate_report
puts Time.now - t
Now you can do:
benchmark :calculate_report # in class
And just call your method
user.calculate_report

Related

Approximate comparison of 2 Ruby DateTimes

I have 2 DateTimes and I want to check if they're roughly 24 hours apart, plus or minus a small amount, say 5 minutes. Is there a built in way to do this?
There is not, but it is easy enough:
(-5 * 60 .. 5 * 60).include?((t2 - t1).abs - 24 * 3600)
"is the absolute difference between the two dates, when you subtract a full day, within plus or minus five minutes?"
Version 1: Works for both DateTime and Time. Converts everything to seconds.
def time_apart_within_drift?(t1, t2, diff: 24*60*60, drift: 5*60)
(t1.to_i - t2.to_i).abs <= diff + drift
end
As per the Ruby Style Guide, you should almost always use Time instead of DateTime. A Time object can still include the date, and it will make your calculations much cleaner.
This is because subtracting two DateTime objects gives you the difference in days as a Rational object, whereas subtracting two Time objects gives you the difference in seconds as a Float.
This allows you to write your function like so:
def time_apart_within_drift?(t1, t2, diff: 24*60*60, drift: 5*60)
(t1 - t2).abs <= diff + drift
end
# true
time_apart_within_drift?(Time.new(2020,3,5), Time.new(2020,3,4))
# true
time_apart_within_drift?(Time.new(2020,3,5), Time.new(2020,3,3,23,57))
# false
time_apart_within_drift?(Time.new(2020,3,5), Time.new(2020,3,3,23,47))
If using Time as well as rails and/or activesupport, the args can be made more readable using Duration objects:
def time_apart_within_drift?(t1, t2, diff: 24.hours, drift: 5.minutes)
(t1 - t2).abs <= diff + drift
end

Get the most precise time in Ruby

I want to get the most possible precis time using Ruby. For example:
3.times.map do
Thread.new do
# Expect 3 differnt results from each thread
p Time.now.precis_time
end
end.each(&:join)
However, even using the strftime, I still can not achieve the goal. So is there any other way to get this?
The most precise timer available to Ruby is Process::clock_gettime. To avoid losing precision to float rounding, use :nanosecond unit:
3.times { p Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) }
# => 1491185078101717000
# => 1491185078101741000
# => 1491185078101747000
EDIT: This is the same time that is available by Time.now. On Linux, the two have nanosecond precision. However, there is another clock that has nanosecond precision even on OSX: CLOCK_MONOTONIC. This clock does not track time from epoch, but time from "some event", this event normally being your computer's boot time. To get the most precise time, one can take the difference between CLOCK_REALTIME and CLOCK_MONOTONIC and apply it later:
clock_diff = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) -
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
3.times {
nsec = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) + clock_diff
time = Time.at(nsec / 1_000_000_000, nsec % 1_000_000_000 / 1_000.0)
p time.strftime("%Y-%m-%d %H:%i:%s.%N")
}
On Linux, I think the most precise time is just Time.now. The to_r method "is intended to be used to get an accurate value representing the nanoseconds since the Epoch" (from the docs).
t = Time.now
p t.to_r # =>(1491206113721862629/1000000000)
p [t.to_i, t.nsec] # =>[1491206113, 721862629]
On JRuby, you can use java.lang.System.nano_time :
java.lang.System.nano_time - java.lang.System.nano_time
# -15607
to get nanoseconds since a fixed but arbitrary origin. From the documentation :
This method can only be used to measure elapsed time and is not
related to any other notion of system or wall-clock time. The value
returned represents nanoseconds since some fixed but arbitrary origin
time (perhaps in the future, so values may be negative). The same
origin is used by all invocations of this method in an instance of a
Java virtual machine; other virtual machine instances are likely to
use a different origin.
If you want a precise Time with Java < 9, you could use currentTimeMillis :
java.lang.System.current_time_millis
#=> 1491214503112
But then, you wouldn't get more information than from Time.now :
Time.now.to_f
#=> 1491214592.562
So Time.now might be your best bet : it will work on any Ruby version on any system. Note that nanoseconds precision doesn't mean nanoseconds accuracy.
I dare say you could ignore any digit related to a shorter time than milliseconds. You could output the distance between NYC and Los-Angeles in micrometers, it doesn't mean it would be useful though.

Get current system time in milliseconds

In Ruby, what is the right way to get the current system time since epoch(1970) in milliseconds?
I tried Time.now.to_i , it seems not the result I want. I need the result shows milliseconds and with long type, not float or double.
(Time.now.to_f * 1000).to_i
Time.now.to_f shows you the time including decimal numbers. To get number of miliseconds just multiply the time by 1000.
You can combine to_i and usec. The former returns the number of seconds since the Epoch, the latter returns the number of microseconds:
require 'time'
t = Time.at(1473152006, 2000)
t.to_i * 1000 + t.usec / 1000
#=> 1473152006002
This is equivalent to:
t.strftime('%s%L')
#=> "1473152006002"
In some cases like the above, to_f would introduce a slight floating point error:
t.to_f * 1000
#=> 1473152006001.9998
(t.to_f * 1000).to_i
#=> 1473152006001

Ruby - new_offset losing a second

I'm not sure if this behaviour is intended, but it seems a bit weird to me. I'm using the code from How do you get DateTime.parse to return a time in your time zone?
require 'date'
estHoursOffset = +10 # Brisbane/Australia
estOffset = Rational(estHoursOffset, 24)
With some times, the DateTime that's returned is a second earlier:
(DateTime.parse("2012-07-15 16:56:00") - (estHoursOffset/24.0)).new_offset(estOffset)
=> #<DateTime: 2012-07-15T16:55:59+10:00 (2456123.788888889,5/12,2299161)>
But with other times, it seems correct:
(DateTime.parse("2012-07-15 16:16:00") - (estHoursOffset/24.0)).new_offset(estOffset)
=> #<DateTime: 2012-07-15T16:16:00+10:00 (2456123.7611111114,5/12,2299161)>
The program I'm writing only cares about the minutes, which means I'm getting back 16:55 when I want 16:56.
So my questions are;
Is this intentional? (If so, is it documented somewhere - I haven't been able to find anything.)
Is there a simple way of fixing this programmatically? Since I don't care about seconds,I suppose I could "round up" the DateTimes returned, but it'd be good to know if this could bring up any other problems in edge cases.
This is probably because floating point numbers are imprecise - the 10/24.0 you are subtracting cannot be represented exactly.
If instead of subtracting that float you subtracted a rational, ie Rational(estHoursOffset, 24) then you should be ok
I tried both times ("2012-07-15 16:56:00" & "2012-07-15 16:16:00") and Ruby was always yielding the times parsed initially. I don't know mate how you managed to get 1 sec less; it is a miracle!! Only joking :)
If this still is giving you a hard time try getting the date (& time) - simpler like this..:
require 'date'
$date = Time.now #current date/time
puts $date
puts $date.min #if you want to use only the minutes
$date="2012-07-15 16:56:00" #if you want to parse it yourself
Moving on to your questions:
-No this is not international and it could be intermittent as well. I've tested your code above (+10h Australia) & from my location London, England (+1h). ALWAYS GOT the time parsed; never a second less or more.
Now if you need to round up the seconds so you will be 100% sure that each & every time you are getting the same results..:
def round_up(seconds)
divisor = 10**Math.log10(seconds).floor
i = seconds / divisor
remainder = seconds % divisor
if remainder == 0
i * divisor
else
(i + 1) * divisor
end
end
I cannot see why the rounding will cause problems in boundary conditions; as long as you always keep rounding everything! Hope this helps! Good luck mate :)

Check if two timestamps are the same day in Ruby

I'm a bit confused between Date, Datetime, and Time in Ruby. What's more, my application is sensitive to timezones, and I'm not sure how to convert between these three while being timezone-robust.
How can I check if two unix timestamps (seconds since epoch) represent the same day? (I don't actually mind if it uses local time or UTC; while I'd prefer local time, as long as it's consistent, I can design around that).
Using the standard library, convert a Time object to a Date.
require 'date'
Time.at(x).to_date === Time.at(y).to_date
Date has the === method that will be true if two date objects represent the same day.
ActiveSupport defines nice to_date method for Time class. That's how it looks like:
class Time
def to_date
::Date.new(year, month, day)
end
end
Using it you can compare timestamps like that:
Time.at(ts1).to_date === Time.at(ts2).to_date
And here is less controversial way without extending Time class:
t1 = Time.at(ts1) # local time corresponding to given unix timestamp ts1
t2 = Time.at(ts2)
Date.new(t1.year, t1.month, t1.day) === Date.new(t2.year, t2.month, t2.day)
Time.at(ts1).day == Time.at(ts2).day && (ts1 - ts2).abs <= 86400
Or
t1 = Time.at(ts1)
t2 = Time.at(ts2)
t1.yday == t2.yday && t1.year == t2.year
In the first case we make sure that timestamps are no more than day apart (because #day returns day of month and without this additional check Apr 1 would be equal to May 1)
An alternative is to take day of year and make sure that they are of the same year.
These methods work equally well in both 1.8 and 1.9.
We can use beginning_of_day of the time and compare them:
t1.beginning_of_day == t2.beginning_of_day
This way the timezones won't be affected.

Resources