I’m currently working on a project that requires a time based tick. While Time::Span and time.monotonic fill this need functionally, they both do processing on the data that makes them much (20-30%) slower than they could be for my purpose.
Requiring the correct files for LibC call on a single system is easy, but for cross-platform support it gets messy. In my project I’ve extracted the time functions to expose them directly, but it feels hackish and like something that could easily be supported by Crystal directly. These changes would be beneficial to those wanting a more performant time, to create user-defined time structure, as making any future changes to Crystal’s internal time structures easier.
To that end I suggest modifying
https://github.com/crystal-lang/crystal/blob/master/src/crystal/system/unix/time.cr#L16 and https://github.com/crystal-lang/crystal/blob/master/src/crystal/system/win32/time.cr to have two additional functions, tentatively called real_time and mono_time. These two functions would make the system appropriate calls for real and monotonic time, respectively to C and handle errors.
In turn, compute_utc_seconds_and_nanoseconds and monotonic would call these new functions and process the data as before, leaving their usage unchanged.
Purposed changes to /src/crystal/system/unix/time.cr are below to give a better idea of what I’m talking about. Thoughts?
def self.real_time
{% if LibC.methods.includes?("clock_gettime".id) %}
ret = LibC.clock_gettime(LibC::CLOCK_REALTIME, out timespec)
raise Errno.new("clock_gettime") unless ret == 0
timespec
{% else %}
ret = LibC.gettimeofday(out timeval, nil)
raise Errno.new("gettimeofday") unless ret == 0
timeval
{% end %}
end
def self.mono_time
{% if flag?(:darwin) %}
info = mach_timebase_info
total_nanoseconds = LibC.mach_absolute_time * info.numer // info.denom
{% else %}
if LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) == 1
raise Errno.new("clock_gettime(CLOCK_MONOTONIC)")
end
tp
{% end %}
end
def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32}
{% if LibC.methods.includes?("clock_gettime".id) %}
timespec = real_time
{timespec.tv_sec.to_i64 + UnixEpochInSeconds, timespec.tv_nsec.to_i}
{% else %}
timeval = real_time
{timeval.tv_sec.to_i64 + UnixEpochInSeconds, timeval.tv_usec.to_i * 1_000}
{% end %}
end
def self.monotonic : {Int64, Int32}
{% if flag?(:darwin) %}
total_nanoseconds = mono_time
seconds = total_nanoseconds // 1_000_000_000
nanoseconds = total_nanoseconds.remainder(1_000_000_000)
{seconds.to_i64, nanoseconds.to_i32}
{% else %}
tp = mono_time
{tp.tv_sec.to_i64, tp.tv_nsec.to_i32}
{% end %}
end