Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is std::time::Duration as precise as time::precise_time_ns from "time" crate?

Tags:

time

rust

For a long time, standard way to precisely measure time in Rust was time crate and its time::precise_time_ns function. However, time crate is deprecated now, and std library has std::time::Instant intended for measuring elapsed time.

I'm not sure it has the same precision, at least by design. I know it might be a vague question because there are different implementation of both things for different OSes, and implementations may change in different versions, but at least do they have the same purpose? Is std::time::Duration a correct replacement for time::precise_time_ns at least from the viewpoint of their design?

Running this script on my system (Mac OS) outputs quite small durations, so it's probably quite precise:

use std::time::Instant;

fn main() {
    let mut t = Instant::now();
    loop {
        println!("{:?}", t.elapsed());
        t = Instant::now();
    }
}
40ns
42ns
41ns
45ns
40ns
41ns
40ns
40ns
41ns
41ns
41ns
40ns
40ns
40ns
like image 423
kolen Avatar asked Apr 09 '19 00:04

kolen


1 Answers

Yes, with high certainty, std::time::Instant is a correct replacement for time::precise_time_ns, having the same, or better, precision.

As of Rust 1.33.0, time 0.1.41, implementations for most OSes of time::precise_time_ns() and std::time::Instant::now() are the same, with few exceptions.

  • Unix: same, clock_gettime(CLOCK_MONOTONIC, ...)
  • MacOS: same, mach_absolute_time
  • Windows: same, QueryPerformanceCounter
  • Wasm32: time has no implementation, std uses TimeSysCall::perform(TimeClock::Monotonic)
  • Redox: same, the same as unix
  • SGX: implementations differ, probably std has more correct implementation

It's unlikely that in future versions std::time::Instant::now implementations will worsen.

Details of implementations

time crate has all implementations in single file, with cfg flags, standard library has directory per system, with mod.rs where implementation is chosen at compile time (unix also has conditional compilation for mac os inside time.rs).

Unix, "regular", not MacOS or iOS

Both implementations use clock_gettime (3) with CLOCK_MONOTONIC clock_id.

time

#[cfg(all(not(target_os = "macos"), not(target_os = "ios")))]

let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe {
    libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
}
(ts.tv_sec as u64) * 1000000000 + (ts.tv_nsec as u64)

std

#[cfg(unix)] + #[cfg(not(any(target_os = "macos", target_os = "ios")))]

Instant { t: now(libc::CLOCK_MONOTONIC) }

Unix, MacOS or iOS

Both implementations use mach_absolute_time.

BTW, standard clock_gettime(CLOCK_MONOTONIC, ...) works on my system too, Mac OS 10.13.6, but I'm not sure if it's really monotonic.

time

#[cfg(any(target_os = "macos", target_os = "ios"))]

unsafe {
    let time = libc::mach_absolute_time();
    let info = info();
    time * info.numer as u64 / info.denom as u64
}

std

#[cfg(unix)] + #[cfg(any(target_os = "macos", target_os = "ios"))]

Instant { t: unsafe { libc::mach_absolute_time() } }

Windows

Both implementations use QueryPerformanceCounter

time

#[cfg(windows)]

let mut ticks = i64_to_large_integer(0);
unsafe {
    assert!(QueryPerformanceCounter(&mut ticks) == 1);
}
mul_div_i64(large_integer_to_i64(ticks), 1000000000, frequency()) as u64

std

#[cfg(windows)]

let mut t = Instant { t: 0 };
cvt(unsafe {
    c::QueryPerformanceCounter(&mut t.t)
}).unwrap();
t

Wasm32

It's probably for non-web use, and unrelated to web-sys. It time crate it's unimplemented.

time

#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]

unimplemented!()

std

#[cfg(target_arch = "wasm32")]

Instant(TimeSysCall::perform(TimeClock::Monotonic))

Redox

Both implementations use clock_gettime(CLOCK_MONOTONIC, ...), the same as for unux.

time

#[cfg(target_os = "redox")]

let mut ts = syscall::TimeSpec { tv_sec: 0, tv_nsec: 0 };
syscall::clock_gettime(syscall::CLOCK_MONOTONIC, &mut ts).unwrap();
(ts.tv_sec as u64) * 1000000000 + (ts.tv_nsec as u64)

std

#[cfg(target_os = "redox")]

Instant { t: now(syscall::CLOCK_MONOTONIC) }

SGX

Here implementations differ. time crate falls back to std, and uses non-monotonic time (there was no monotonic time in std at the time, probably). Probably migrating from time to std improves accuracy because it uses SGX-specific call.

time

#[cfg(target_env = "sgx")]

// This unwrap is safe because current time is well ahead of UNIX_EPOCH, unless system clock is adjusted backward.
let std_duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
std_duration.as_secs() * NANOS_PER_SEC + std_duration.subsec_nanos() as u64

std

#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]

Instant(usercalls::insecure_time())
like image 64
kolen Avatar answered Nov 09 '22 09:11

kolen