std::time::Duration 是否与 time::precise_time_ns 来自“时间”板条箱一样精确?

kol*_*len 5 time rust

长期以来,在 Rust 中精确测量时间的标准方法是timecrate 及其time::precise_time_ns功能。但是,time现在不推荐使用 crate,并且stdlibrarystd::time::Instant旨在测量经过的时间。

我不确定它是否具有相同的精度,至少在设计上是这样。我知道这可能是一个模糊的问题,因为对于不同的操作系统,这两种东西都有不同的实现,而且不同版本的实现可能会有所不同,但至少它们有相同的目的吗?至少从其设计的角度来看是std::time::Duration正确的替代品time::precise_time_ns吗?

在我的系统 (Mac OS) 上运行此脚本输出的持续时间非常短,因此它可能非常精确:

use std::time::Instant;

fn main() {
    let mut t = Instant::now();
    loop {
        println!("{:?}", t.elapsed());
        t = Instant::now();
    }
}
Run Code Online (Sandbox Code Playgroud)
40ns
42ns
41ns
45ns
40ns
41ns
40ns
40ns
41ns
41ns
41ns
40ns
40ns
40ns
Run Code Online (Sandbox Code Playgroud)

kol*_*len 5

是的,非常确定,std::time::Instant是 的正确替代品time::precise_time_ns,具有相同或更好的精度。

铁锈1.33.0的time0.1.41,对于大多数操作系统实现time::precise_time_ns()std::time::Instant::now()是相同的,除了少数例外。

  • Unix相同clock_gettime(CLOCK_MONOTONIC, ...)
  • MacOS相同mach_absolute_time
  • 窗户相同QueryPerformanceCounter
  • Wasm32time没有实现,std使用TimeSysCall::perform(TimeClock::Monotonic)
  • 氧化还原相同,与 unix 相同
  • SGX实现不同,可能std有更正确的实现

未来版本的std::time::Instant::now实现不太可能恶化。

实现细节

timecrate 在单个文件中具有所有实现,带有 cfg 标志,标准库具有每个系统的目录,在mod.rs 中在编译时选择实现(unix 也有针对内部 mac os 的条件编译time.rs)。

Unix,“常规”,而不是MacOS 或 iOS

两种实现都使用clock_gettime (3)with CLOCK_MONOTONIC clock_id

时间

#[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)
Run Code Online (Sandbox Code Playgroud)

标准

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

Instant { t: now(libc::CLOCK_MONOTONIC) }
Run Code Online (Sandbox Code Playgroud)

Unix、MacOS 或 iOS

两种实现都使用mach_absolute_time.

顺便说一句,标准也clock_gettime(CLOCK_MONOTONIC, ...)适用于我的系统,Mac OS 10.13.6,但我不确定它是否真的单调。

时间

#[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
}
Run Code Online (Sandbox Code Playgroud)

标准

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

Instant { t: unsafe { libc::mach_absolute_time() } }
Run Code Online (Sandbox Code Playgroud)

视窗

两种实现都使用 QueryPerformanceCounter

时间

#[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
Run Code Online (Sandbox Code Playgroud)

标准

#[cfg(windows)]

let mut t = Instant { t: 0 };
cvt(unsafe {
    c::QueryPerformanceCounter(&mut t.t)
}).unwrap();
t
Run Code Online (Sandbox Code Playgroud)

WASM32

它可能用于非网络用途,与web-sys无关。它time没有实现。

时间

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

unimplemented!()
Run Code Online (Sandbox Code Playgroud)

标准

#[cfg(target_arch = "wasm32")]

Instant(TimeSysCall::perform(TimeClock::Monotonic))
Run Code Online (Sandbox Code Playgroud)

氧化还原

两种实现都使用clock_gettime(CLOCK_MONOTONIC, ...),与 unux 相同。

时间

#[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)
Run Code Online (Sandbox Code Playgroud)

标准

#[cfg(target_os = "redox")]

Instant { t: now(syscall::CLOCK_MONOTONIC) }
Run Code Online (Sandbox Code Playgroud)

新交所

这里的实现有所不同。timecrate 回退到 std,并使用非单调时间(当时 std 中可能没有单调时间)。可能不时迁移到 std 会提高准确性,因为它使用 SGX 特定的调用。

时间

#[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
Run Code Online (Sandbox Code Playgroud)

标准

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

Instant(usercalls::insecure_time())
Run Code Online (Sandbox Code Playgroud)