├── .github ├── dependabot.yml └── workflows │ └── issues.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor.yml ├── benches └── benchmark.rs └── src ├── clock.rs ├── duration.rs ├── helpers.rs ├── instant.rs ├── lib.rs ├── tests.rs └── updater.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v9 14 | with: 15 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 16 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .vscode 4 | zig-cache 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coarsetime" 3 | version = "0.1.36" 4 | description = "Time and duration crate optimized for speed" 5 | authors = ["Frank Denis "] 6 | keywords = ["time", "date", "duration"] 7 | readme = "README.md" 8 | license = "ISC" 9 | homepage = "https://github.com/jedisct1/rust-coarsetime" 10 | repository = "https://github.com/jedisct1/rust-coarsetime" 11 | categories = ["concurrency", "date-and-time", "os"] 12 | edition = "2018" 13 | 14 | [badges] 15 | appveyor = { repository = "jedisct1/rust-coarsetime" } 16 | 17 | [features] 18 | wasi-abi2 = ["dep:wasi-abi2"] 19 | 20 | [target.'cfg(not(any(target_os = "wasix", target_os = "wasi")))'.dependencies] 21 | libc = "0.2" 22 | 23 | [target.'cfg(target_os = "wasi")'.dependencies] 24 | wasi-abi2 = { package = "wasi", version = "0.14.2", optional = true } 25 | 26 | [target.'cfg(any(target_os = "wasix", target_os = "wasi"))'.dependencies] 27 | wasix = "0.12" 28 | 29 | [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] 30 | wasm-bindgen = "0.2" 31 | 32 | [dev-dependencies] 33 | benchmark-simple = "0.1.10" 34 | 35 | [profile.bench] 36 | codegen-units = 1 37 | 38 | [[bench]] 39 | name = "benchmark" 40 | harness = false 41 | 42 | [profile.release] 43 | lto = true 44 | panic = "abort" 45 | opt-level = 3 46 | codegen-units = 1 47 | incremental = false 48 | 49 | [profile.dev] 50 | overflow-checks=true 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2016-2025, Frank Denis 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation](https://docs.rs/coarsetime/badge.svg)](https://docs.rs/coarsetime) 2 | [![Windows build status](https://ci.appveyor.com/api/projects/status/xlbhk9850dvl5ylh?svg=true)](https://ci.appveyor.com/project/jedisct1/rust-coarsetime) 3 | # coarsetime 4 | 5 | A Rust crate to make time measurements, that focuses on speed, API stability and portability. 6 | 7 | This crate is a partial replacement for the `Time` and `Duration` structures 8 | from the standard library, with the following differences: 9 | 10 | * Speed is privileged over accuracy. In particular, `CLOCK_MONOTONIC_COARSE` is 11 | used to retrieve the clock value on Linux systems, and transformations avoid 12 | operations that can be slow on non-Intel systems. 13 | * The number of system calls can be kept to a minimum. The "most recent 14 | timestamp" is always kept in memory. It can be read with just a load operation, 15 | and can be updated only as frequently as necessary. 16 | * The API is stable, and the same for all platforms. Unlike the standard library, it doesn't silently compile functions that do nothing but panic at runtime on some platforms. 17 | 18 | # Installation 19 | 20 | `coarsetime` is available on [crates.io](https://crates.io/crates/coarsetime) 21 | and works on Rust stable, beta, and nightly. 22 | 23 | Windows and Unix-like systems are supported. 24 | 25 | Available feature: 26 | 27 | * `wasi-abi2`: when targeting WASI, use the second preview of the ABI. Default is to use the regular WASI-core ABI. 28 | 29 | # Documentation 30 | 31 | [API documentation](https://docs.rs/coarsetime) 32 | 33 | # Example 34 | 35 | ```rust 36 | extern crate coarsetime; 37 | 38 | use coarsetime::{Duration, Instant, Updater}; 39 | 40 | // Get the current instant. This may require a system call, but it may also 41 | // be faster than the stdlib equivalent. 42 | let now = Instant::now(); 43 | 44 | // Get the latest known instant. This operation is super fast. 45 | // In this case, the value will be identical to `now`, because we haven't 46 | // updated the latest known instant yet. 47 | let ts1 = Instant::recent(); 48 | 49 | // Update the latest known instant. This may require a system call. 50 | // Note that a call to `Instant::now()` also updates the stored instant. 51 | Instant::update(); 52 | 53 | // Now, we may get a different instant. This call is also super fast. 54 | let ts2 = Instant::recent(); 55 | 56 | // Compute the time elapsed between ts2 and ts1. 57 | let elapsed_ts2_ts1 = ts2.duration_since(ts1); 58 | 59 | // Operations such as `+` and `-` between `Instant` and `Duration` are also 60 | // available. 61 | let elapsed_ts2_ts1 = ts2 - ts1; 62 | 63 | // Returns the time elapsed since ts1. 64 | // This retrieves the actual current time, and may require a system call. 65 | let elapsed_since_ts1 = ts1.elapsed(); 66 | 67 | // Returns the approximate time elapsed since ts1. 68 | // This uses the latest known instant, and is super fast. 69 | let elapsed_since_recent = ts1.elapsed_since_recent(); 70 | 71 | // Instant::update() should be called periodically, for example using an 72 | // event loop. Alternatively, the crate provides an easy way to spawn a 73 | // background task that will periodically update the latest known instant. 74 | // Here, the update will happen every 250ms. 75 | let updater = Updater::new(250).start().unwrap(); 76 | 77 | // From now on, Instant::recent() will always return an approximation of the 78 | // current instant. 79 | let ts3 = Instant::recent(); 80 | 81 | // Stop the task. 82 | updater.stop().unwrap(); 83 | 84 | // Returns the elapsed time since the UNIX epoch 85 | let unix_timestamp = Clock::now_since_epoch(); 86 | 87 | // Returns an approximation of the elapsed time since the UNIX epoch, based on 88 | // the latest time update 89 | let unix_timestamp_approx = Clock::recent_since_epoch(); 90 | ``` 91 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | PROJECT_NAME: rust-coarsetime 4 | 5 | matrix: 6 | - TARGET: i686-pc-windows-gnu 7 | CHANNEL: nightly 8 | - TARGET: i686-pc-windows-msvc 9 | CHANNEL: nightly 10 | - TARGET: x86_64-pc-windows-gnu 11 | CHANNEL: nightly 12 | - TARGET: x86_64-pc-windows-msvc 13 | CHANNEL: nightly 14 | - TARGET: i686-pc-windows-gnu 15 | CHANNEL: stable 16 | - TARGET: i686-pc-windows-msvc 17 | CHANNEL: stable 18 | - TARGET: x86_64-pc-windows-gnu 19 | CHANNEL: stable 20 | - TARGET: x86_64-pc-windows-msvc 21 | CHANNEL: stable 22 | 23 | install: 24 | - curl -sSf -o rustup-init.exe https://win.rustup.rs 25 | - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y 26 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 27 | - rustc -Vv 28 | - cargo -V 29 | 30 | build: false 31 | 32 | test_script: 33 | - cargo build --verbose 34 | - cargo doc 35 | - cargo test 36 | -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use benchmark_simple::*; 2 | use coarsetime::*; 3 | use std::time; 4 | 5 | fn main() { 6 | let options = &Options { 7 | iterations: 250_000, 8 | warmup_iterations: 25_000, 9 | min_samples: 5, 10 | max_samples: 10, 11 | max_rsd: 1.0, 12 | ..Default::default() 13 | }; 14 | bench_coarsetime_now(options); 15 | bench_coarsetime_recent(options); 16 | bench_coarsetime_elapsed(options); 17 | bench_coarsetime_elapsed_since_recent(options); 18 | bench_stdlib_now(options); 19 | bench_stdlib_elapsed(options); 20 | } 21 | 22 | fn bench_coarsetime_now(options: &Options) { 23 | let b = Bench::new(); 24 | Instant::update(); 25 | let res = b.run(options, Instant::now); 26 | println!("coarsetime_now(): {}", res.throughput(1)); 27 | } 28 | 29 | fn bench_coarsetime_recent(options: &Options) { 30 | let b = Bench::new(); 31 | Instant::update(); 32 | let res = b.run(options, Instant::recent); 33 | println!("coarsetime_recent(): {}", res.throughput(1)); 34 | } 35 | 36 | fn bench_coarsetime_elapsed(options: &Options) { 37 | let b = Bench::new(); 38 | let ts = Instant::now(); 39 | let res = b.run(options, || ts.elapsed()); 40 | println!("coarsetime_elapsed(): {}", res.throughput(1)); 41 | } 42 | 43 | fn bench_coarsetime_elapsed_since_recent(options: &Options) { 44 | let b = Bench::new(); 45 | let ts = Instant::now(); 46 | let res = b.run(options, || ts.elapsed_since_recent()); 47 | println!("coarsetime_since_recent(): {}", res.throughput(1)); 48 | } 49 | 50 | fn bench_stdlib_now(options: &Options) { 51 | let b = Bench::new(); 52 | let res = b.run(options, time::Instant::now); 53 | println!("stdlib_now(): {}", res.throughput(1)); 54 | } 55 | 56 | fn bench_stdlib_elapsed(options: &Options) { 57 | let b = Bench::new(); 58 | let ts = time::Instant::now(); 59 | let res = b.run(options, || ts.elapsed()); 60 | println!("stdlib_elapsed(): {}", res.throughput(1)); 61 | } 62 | -------------------------------------------------------------------------------- /src/clock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(all( 2 | any(target_arch = "wasm32", target_arch = "wasm64"), 3 | target_os = "unknown" 4 | )))] 5 | use std::time; 6 | 7 | use std::sync::atomic::{AtomicU64, Ordering}; 8 | 9 | use super::Duration; 10 | 11 | static RECENT: AtomicU64 = AtomicU64::new(0); 12 | 13 | #[cfg(all( 14 | any(target_arch = "wasm32", target_arch = "wasm64"), 15 | target_os = "unknown" 16 | ))] 17 | mod js_imports { 18 | use wasm_bindgen::prelude::*; 19 | 20 | #[wasm_bindgen] 21 | extern "C" { 22 | pub type Date; 23 | 24 | #[wasm_bindgen(static_method_of = Date)] 25 | pub fn now() -> f64; 26 | } 27 | } 28 | 29 | /// System time 30 | #[derive(Debug)] 31 | pub struct Clock; 32 | 33 | /// Alias for `Duration`. 34 | pub type UnixTimeStamp = Duration; 35 | 36 | impl Clock { 37 | /// Returns the elapsed time since the UNIX epoch 38 | #[inline] 39 | pub fn now_since_epoch() -> UnixTimeStamp { 40 | Duration::from_u64(unix_ts()) 41 | } 42 | 43 | /// Returns the elapsed time since the UNIX epoch, based on the latest 44 | /// explicit time update 45 | #[inline] 46 | pub fn recent_since_epoch() -> UnixTimeStamp { 47 | Duration::from_u64(RECENT.load(Ordering::Relaxed)) 48 | } 49 | 50 | /// Updates the cached system time. 51 | /// 52 | /// This function should be called frequently, for example in an event loop 53 | /// or using an `Updater` task. 54 | #[inline] 55 | pub fn update() { 56 | let now = unix_ts(); 57 | RECENT.store(now, Ordering::Relaxed) 58 | } 59 | 60 | /// Sets the cached system time to the specified timestamp. 61 | /// This function is intended for testing purposes only. 62 | /// It should not be used in production code. 63 | pub fn set_recent_since_epoch(recent: UnixTimeStamp) { 64 | RECENT.store(recent.as_u64(), Ordering::Relaxed) 65 | } 66 | } 67 | 68 | #[cfg(all( 69 | any(target_arch = "wasm32", target_arch = "wasm64"), 70 | target_os = "unknown" 71 | ))] 72 | #[inline] 73 | fn unix_ts() -> u64 { 74 | let unix_ts_now_sys = (js_imports::Date::now() / 1000.0).round() as u64; 75 | let unix_ts_now = Duration::from_secs(unix_ts_now_sys); 76 | unix_ts_now.as_u64() 77 | } 78 | 79 | #[cfg(not(all( 80 | any(target_arch = "wasm32", target_arch = "wasm64"), 81 | target_os = "unknown" 82 | )))] 83 | #[inline] 84 | fn unix_ts() -> u64 { 85 | let unix_ts_now_sys = time::SystemTime::now() 86 | .duration_since(time::UNIX_EPOCH) 87 | .expect("The system clock is not properly set"); 88 | let unix_ts_now = Duration::from(unix_ts_now_sys); 89 | unix_ts_now.as_u64() 90 | } 91 | -------------------------------------------------------------------------------- /src/duration.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::ops::*; 3 | use std::time; 4 | 5 | use super::helpers::*; 6 | 7 | /// A duration type to represent an approximate span of time 8 | #[derive(Copy, Clone, Debug, Hash, Ord, Eq, PartialOrd, PartialEq, Default)] 9 | pub struct Duration(u64); 10 | 11 | impl Duration { 12 | /// Creates a new `Duration` from the specified number of seconds and 13 | /// additional nanosecond precision 14 | #[inline] 15 | pub fn new(sec: u64, nanos: u32) -> Duration { 16 | Duration(_timespec_to_u64(sec, nanos)) 17 | } 18 | 19 | /// Creates a new Duration from the specified number of days 20 | #[inline] 21 | pub fn from_days(days: u64) -> Duration { 22 | Duration(_sec_to_u64(days * 86400)) 23 | } 24 | 25 | /// Creates a new Duration from the specified number of hours 26 | #[inline] 27 | pub fn from_hours(hours: u64) -> Duration { 28 | Duration(_sec_to_u64(hours * 3600)) 29 | } 30 | 31 | /// Creates a new Duration from the specified number of minutes 32 | #[inline] 33 | pub fn from_mins(mins: u64) -> Duration { 34 | Duration(_sec_to_u64(mins * 60)) 35 | } 36 | 37 | /// Creates a new Duration from the specified number of seconds 38 | #[inline] 39 | pub fn from_secs(secs: u64) -> Duration { 40 | Duration(_sec_to_u64(secs)) 41 | } 42 | 43 | /// Creates a new Duration from the specified number of milliseconds 44 | #[inline] 45 | pub fn from_millis(millis: u64) -> Duration { 46 | Duration(_millis_to_u64(millis)) 47 | } 48 | 49 | /// Returns the number of days represented by this duration 50 | #[inline] 51 | pub fn as_days(&self) -> u64 { 52 | self.as_secs() / 86400 53 | } 54 | 55 | /// Returns the number of minutes represented by this duration 56 | #[inline] 57 | pub fn as_hours(&self) -> u64 { 58 | self.as_secs() / 3600 59 | } 60 | 61 | /// Returns the number of minutes represented by this duration 62 | #[inline] 63 | pub fn as_mins(&self) -> u64 { 64 | self.as_secs() / 60 65 | } 66 | 67 | /// Returns the number of whole seconds represented by this duration 68 | #[inline] 69 | pub fn as_secs(&self) -> u64 { 70 | self.0 >> 32 71 | } 72 | 73 | /// Returns the number of whole milliseconds represented by this duration 74 | #[inline] 75 | pub fn as_millis(&self) -> u64 { 76 | ((self.0 as u128 * 125) >> 29) as u64 77 | } 78 | 79 | /// Returns the number of whole microseconds represented by this duration 80 | #[inline] 81 | pub fn as_micros(&self) -> u64 { 82 | ((self.0 as u128 * 125_000) >> 29) as u64 83 | } 84 | 85 | /// Returns the number of whole nanoseconds represented by this duration 86 | #[inline] 87 | pub fn as_nanos(&self) -> u64 { 88 | ((self.0 as u128 * 125_000_000) >> 29) as u64 89 | } 90 | 91 | /// Returns the nanosecond precision represented by this duration 92 | #[inline] 93 | pub fn subsec_nanos(&self) -> u32 { 94 | ((self.0 as u32 as u64 * 125_000_000) >> 29) as u32 95 | } 96 | 97 | /// Return this duration as a number of "ticks". 98 | /// 99 | /// Note that length of a 'tick' is not guaranteed to represent 100 | /// the same amount of time across different platforms, or from 101 | /// one version of `coarsetime` to another. 102 | #[inline] 103 | pub fn as_ticks(&self) -> u64 { 104 | self.as_u64() 105 | } 106 | 107 | /// Creates a new Duration from the specified number of "ticks". 108 | /// 109 | /// Note that length of a 'tick' is not guaranteed to represent 110 | /// the same amount of time across different platforms, or from 111 | /// one version of `coarsetime` to another. 112 | #[inline] 113 | pub fn from_ticks(ticks: u64) -> Duration { 114 | Self::from_u64(ticks) 115 | } 116 | 117 | #[doc(hidden)] 118 | #[inline] 119 | pub fn as_u64(&self) -> u64 { 120 | self.0 121 | } 122 | 123 | #[doc(hidden)] 124 | #[inline] 125 | pub fn from_u64(ts: u64) -> Duration { 126 | Duration(ts) 127 | } 128 | 129 | /// Returns the duration as a floating point number, representing the number 130 | /// of seconds 131 | #[inline] 132 | pub fn as_f64(&self) -> f64 { 133 | (self.0 as f64) / ((1u64 << 32) as f64) 134 | } 135 | 136 | /// Returns the absolute difference between two `Duration`s 137 | #[inline] 138 | pub fn abs_diff(&self, other: Duration) -> Duration { 139 | Duration(self.0.abs_diff(other.0)) 140 | } 141 | 142 | /// Add two durations, saturating on overflow 143 | #[inline] 144 | pub fn saturating_add(self, rhs: Duration) -> Duration { 145 | Duration(self.0.saturating_add(rhs.0)) 146 | } 147 | 148 | /// Add two durations, returning `None` on overflow 149 | #[inline] 150 | pub fn checked_add(self, rhs: Duration) -> Option { 151 | self.0.checked_add(rhs.0).map(Duration) 152 | } 153 | 154 | /// Subtract two durations, saturating on underflow/overflow 155 | #[inline] 156 | pub fn saturating_sub(self, rhs: Duration) -> Duration { 157 | Duration(self.0.saturating_sub(rhs.0)) 158 | } 159 | 160 | /// Subtract two durations, returning `None` on underflow/overflow 161 | #[inline] 162 | pub fn checked_sub(self, rhs: Duration) -> Option { 163 | self.0.checked_sub(rhs.0).map(Duration) 164 | } 165 | 166 | /// Multiply a duration by a scalar, saturating on overflow 167 | #[inline] 168 | pub fn saturating_mul(self, rhs: u32) -> Duration { 169 | Duration(self.0.saturating_mul(rhs as u64)) 170 | } 171 | 172 | /// Multiply a duration by a scalar, returning `None` on overflow 173 | #[inline] 174 | pub fn checked_mul(self, rhs: u32) -> Option { 175 | self.0.checked_mul(rhs as u64).map(Duration) 176 | } 177 | 178 | /// Divide a duration by a scalar, returning `None` for division by zero 179 | #[inline] 180 | pub fn checked_div(self, rhs: u32) -> Option { 181 | self.0.checked_div(rhs as u64).map(Duration) 182 | } 183 | } 184 | 185 | #[doc(hidden)] 186 | impl From for Duration { 187 | #[doc(hidden)] 188 | #[inline] 189 | fn from(ts: u64) -> Duration { 190 | Duration::from_u64(ts) 191 | } 192 | } 193 | 194 | impl Add for Duration { 195 | type Output = Duration; 196 | 197 | #[inline] 198 | fn add(self, rhs: Duration) -> Duration { 199 | Duration(self.0 + rhs.0) 200 | } 201 | } 202 | 203 | impl AddAssign for Duration { 204 | #[inline] 205 | fn add_assign(&mut self, rhs: Duration) { 206 | *self = *self + rhs; 207 | } 208 | } 209 | 210 | impl Sub for Duration { 211 | type Output = Duration; 212 | 213 | #[inline] 214 | fn sub(self, rhs: Duration) -> Duration { 215 | Duration(self.0 - rhs.0) 216 | } 217 | } 218 | 219 | impl SubAssign for Duration { 220 | #[inline] 221 | fn sub_assign(&mut self, rhs: Duration) { 222 | *self = *self - rhs; 223 | } 224 | } 225 | 226 | impl Mul for Duration { 227 | type Output = Duration; 228 | 229 | #[inline] 230 | fn mul(self, rhs: u32) -> Duration { 231 | Duration(self.0 * rhs as u64) 232 | } 233 | } 234 | 235 | impl MulAssign for Duration { 236 | #[inline] 237 | fn mul_assign(&mut self, rhs: u32) { 238 | *self = *self * rhs; 239 | } 240 | } 241 | 242 | impl Div for Duration { 243 | type Output = Duration; 244 | 245 | #[inline] 246 | fn div(self, rhs: u32) -> Duration { 247 | Duration(self.0 / rhs as u64) 248 | } 249 | } 250 | 251 | impl DivAssign for Duration { 252 | #[inline] 253 | fn div_assign(&mut self, rhs: u32) { 254 | *self = *self / rhs; 255 | } 256 | } 257 | 258 | impl From for time::Duration { 259 | #[inline] 260 | fn from(duration: Duration) -> time::Duration { 261 | time::Duration::new(duration.as_secs(), duration.subsec_nanos()) 262 | } 263 | } 264 | 265 | impl From for Duration { 266 | #[inline] 267 | fn from(duration_sys: time::Duration) -> Duration { 268 | Duration::new(duration_sys.as_secs(), duration_sys.subsec_nanos()) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | #[inline] 2 | pub fn _sec_to_u64(sec: u64) -> u64 { 3 | sec.saturating_mul(1 << 32) 4 | } 5 | 6 | #[inline] 7 | pub fn _millis_to_u64(millis: u64) -> u64 { 8 | let secs = millis / 1_000; 9 | secs.saturating_mul(1 << 32) | ((millis - secs * 1_000) << 22) 10 | } 11 | 12 | #[inline] 13 | pub fn _nsecs_to_u64(nsecs: u64) -> u64 { 14 | let secs = nsecs / 1_000_000_000; 15 | _timespec_to_u64(secs, (nsecs - secs * 1_000_000_000) as u32) 16 | } 17 | 18 | #[inline] 19 | pub fn _timespec_to_u64(tp_sec: u64, tp_nsec: u32) -> u64 { 20 | tp_sec.saturating_mul(1 << 32) | ((tp_nsec as u64 * 9_223_372_037) >> 31) 21 | } 22 | 23 | #[inline] 24 | pub fn _timeval_to_u64(tv_sec: u64, tv_usec: u32) -> u64 { 25 | tv_sec.saturating_mul(1 << 32) | ((tv_usec as u64 * 9_223_372_036_855) >> 31) 26 | } 27 | -------------------------------------------------------------------------------- /src/instant.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use std::mem::MaybeUninit; 3 | use std::ops::*; 4 | #[allow(unused_imports)] 5 | use std::ptr::*; 6 | use std::sync::atomic::{AtomicU64, Ordering}; 7 | 8 | use super::duration::*; 9 | #[allow(unused_imports)] 10 | use super::helpers::*; 11 | 12 | /// A measurement of a *monotonically* increasing clock. 13 | /// Opaque and useful only with `Duration`. 14 | /// 15 | /// Resulting durations are actual durations; they do not get affected by 16 | /// clock adjustments, leap seconds, or similar. 17 | /// In order to get a measurement of the *wall clock*, use `Date` instead. 18 | #[derive(Copy, Clone, Debug, Hash, Ord, Eq, PartialOrd, PartialEq)] 19 | pub struct Instant(u64); 20 | 21 | static RECENT: AtomicU64 = AtomicU64::new(0); 22 | 23 | #[cfg(windows)] 24 | extern "system" { 25 | pub fn GetTickCount64() -> libc::c_ulonglong; 26 | } 27 | 28 | #[cfg(any(target_os = "macos", target_os = "freebsd"))] 29 | #[allow(non_camel_case_types)] 30 | type clockid_t = libc::c_int; 31 | 32 | #[cfg(target_os = "macos")] 33 | const CLOCK_MONOTONIC_RAW_APPROX: clockid_t = 5; 34 | 35 | #[cfg(target_os = "macos")] 36 | extern "system" { 37 | fn clock_gettime_nsec_np(clk_id: clockid_t) -> u64; 38 | } 39 | 40 | #[cfg(target_os = "freebsd")] 41 | const CLOCK_MONOTONIC_FAST: clockid_t = 12; 42 | 43 | #[cfg(all( 44 | any(target_arch = "wasm32", target_arch = "wasm64"), 45 | target_os = "unknown" 46 | ))] 47 | mod js_imports { 48 | use wasm_bindgen::prelude::*; 49 | 50 | #[wasm_bindgen] 51 | extern "C" { 52 | #[allow(non_camel_case_types)] 53 | pub type performance; 54 | 55 | #[wasm_bindgen(static_method_of = performance)] 56 | pub fn now() -> f64; 57 | } 58 | } 59 | 60 | impl Instant { 61 | /// Returns an instant corresponding to "now" 62 | /// 63 | /// This function also updates the stored instant. 64 | pub fn now() -> Instant { 65 | let now = Self::_now(); 66 | Self::_update(now); 67 | Instant(now) 68 | } 69 | 70 | /// Returns an instant corresponding to "now" without updating the cached value. 71 | /// After this, `recent()` will still return the old instant. 72 | /// 73 | /// `now()` is generally preferred over this function. 74 | pub fn now_without_cache_update() -> Instant { 75 | let now = Self::_now(); 76 | Instant(now) 77 | } 78 | 79 | /// Returns an instant corresponding to the latest update 80 | pub fn recent() -> Instant { 81 | match Self::_recent() { 82 | 0 => Instant::now(), 83 | recent => Instant(recent), 84 | } 85 | } 86 | 87 | /// Update the stored instant 88 | /// 89 | /// This function should be called frequently, for example in an event loop 90 | /// or using an `Updater` task. 91 | pub fn update() { 92 | let now = Self::_now(); 93 | Self::_update(now); 94 | } 95 | 96 | /// Returns the amount of time elapsed from another instant to this one 97 | #[inline] 98 | pub fn duration_since(&self, earlier: Instant) -> Duration { 99 | *self - earlier 100 | } 101 | 102 | /// Returns the amount of time elapsed between the this instant was created 103 | /// and the latest update 104 | #[inline] 105 | pub fn elapsed_since_recent(&self) -> Duration { 106 | Self::recent() - *self 107 | } 108 | 109 | /// Returns the amount of time elapsed since this instant was created 110 | /// 111 | /// This function also updates the stored instant. 112 | #[inline] 113 | pub fn elapsed(&self) -> Duration { 114 | Self::now() - *self 115 | } 116 | 117 | /// Return a representation of this instant as a number of "ticks". 118 | /// 119 | /// Note that length of a 'tick' is not guaranteed to represent 120 | /// the same amount of time across different platforms, or from 121 | /// one version of `coarsetime` to another. 122 | /// 123 | /// Note also that the instant represented by "0" ticks is 124 | /// unspecified. It is not guaranteed to be the same time across 125 | /// different platforms, or from one version of `coarsetime` to 126 | /// another. 127 | /// 128 | /// This API is mainly intended for applications that need to 129 | /// store the value of an `Instant` in an 130 | /// [`AtomicU64`](std::sync::atomic::AtomicU64). 131 | #[inline] 132 | pub fn as_ticks(&self) -> u64 { 133 | self.as_u64() 134 | } 135 | 136 | #[doc(hidden)] 137 | #[inline] 138 | pub fn as_u64(&self) -> u64 { 139 | self.0 140 | } 141 | 142 | /// Calculate an `Instant` that is a `Duration` later, saturating on overflow 143 | #[inline] 144 | pub fn saturating_add(self, rhs: Duration) -> Instant { 145 | Instant(self.0.saturating_add(rhs.as_u64())) 146 | } 147 | 148 | /// Calculate an `Instant` that is a `Duration` later, returning `None` on overflow 149 | #[inline] 150 | pub fn checked_add(self, rhs: Duration) -> Option { 151 | self.0.checked_add(rhs.as_u64()).map(Instant) 152 | } 153 | 154 | /// Calculate an `Instant` that is a `Duration` earlier, saturating on underflow 155 | #[inline] 156 | pub fn saturating_sub(self, rhs: Duration) -> Instant { 157 | Instant(self.0.saturating_sub(rhs.as_u64())) 158 | } 159 | 160 | /// Calculate an `Instant` that is a `Duration` earlier, returning `None` on underflow 161 | #[inline] 162 | pub fn checked_sub(self, rhs: Duration) -> Option { 163 | self.0.checked_sub(rhs.as_u64()).map(Instant) 164 | } 165 | 166 | #[cfg(any(target_os = "linux", target_os = "android"))] 167 | fn _now() -> u64 { 168 | let mut tp = MaybeUninit::::uninit(); 169 | let tp = unsafe { 170 | libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()); 171 | tp.assume_init() 172 | }; 173 | _timespec_to_u64(tp.tv_sec as u64, tp.tv_nsec as u32) 174 | } 175 | 176 | #[cfg(target_os = "macos")] 177 | fn _now() -> u64 { 178 | let nsec = unsafe { clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX) }; 179 | _nsecs_to_u64(nsec) 180 | } 181 | 182 | #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] 183 | fn _now() -> u64 { 184 | let mut tp = MaybeUninit::::uninit(); 185 | let tp = unsafe { 186 | libc::clock_gettime(libc::CLOCK_MONOTONIC_FAST, tp.as_mut_ptr()); 187 | tp.assume_init() 188 | }; 189 | _timespec_to_u64(tp.tv_sec as u64, tp.tv_nsec as u32) 190 | } 191 | 192 | #[cfg(all( 193 | unix, 194 | not(any( 195 | target_os = "macos", 196 | target_os = "linux", 197 | target_os = "android", 198 | target_os = "freebsd", 199 | target_os = "dragonfly" 200 | )) 201 | ))] 202 | fn _now() -> u64 { 203 | let mut tv = MaybeUninit::::uninit(); 204 | let tv = unsafe { 205 | libc::gettimeofday(tv.as_mut_ptr(), null_mut()); 206 | tv.assume_init() 207 | }; 208 | _timeval_to_u64(tv.tv_sec as u64, tv.tv_usec as u32) 209 | } 210 | 211 | #[cfg(windows)] 212 | fn _now() -> u64 { 213 | let tc = unsafe { GetTickCount64() } as u64; 214 | _millis_to_u64(tc) 215 | } 216 | 217 | #[cfg(all(target_os = "wasi", not(feature = "wasi-abi2")))] 218 | fn _now() -> u64 { 219 | use wasix::{clock_time_get, CLOCKID_MONOTONIC, CLOCKID_REALTIME}; 220 | let nsec = unsafe { clock_time_get(CLOCKID_MONOTONIC, 1_000_000) } 221 | .or_else(|_| unsafe { clock_time_get(CLOCKID_REALTIME, 1_000_000) }) 222 | .expect("Clock not available"); 223 | _nsecs_to_u64(nsec) 224 | } 225 | 226 | #[cfg(all(target_os = "wasi", feature = "wasi-abi2"))] 227 | fn _now() -> u64 { 228 | let nsec = wasi_abi2::clocks::monotonic_clock::now(); 229 | _nsecs_to_u64(nsec) 230 | } 231 | 232 | #[cfg(all( 233 | any(target_arch = "wasm32", target_arch = "wasm64"), 234 | target_os = "unknown" 235 | ))] 236 | fn _now() -> u64 { 237 | _millis_to_u64(js_imports::performance::now() as u64) 238 | } 239 | 240 | #[cfg(all(target_arch = "x86_64", target_env = "sgx", target_vendor = "fortanix"))] 241 | fn _now() -> u64 { 242 | let timestamp = std::time::SystemTime::now() 243 | .duration_since(std::time::UNIX_EPOCH) 244 | .unwrap(); 245 | timestamp.as_secs() * 1_000_000_000 + (timestamp.subsec_nanos() as u64) 246 | } 247 | 248 | #[inline] 249 | fn _update(now: u64) { 250 | RECENT.store(now, Ordering::Relaxed) 251 | } 252 | 253 | #[inline] 254 | fn _recent() -> u64 { 255 | let recent = RECENT.load(Ordering::Relaxed); 256 | if recent != 0 { 257 | recent 258 | } else { 259 | let now = Self::_now(); 260 | Self::_update(now); 261 | Self::_recent() 262 | } 263 | } 264 | } 265 | 266 | impl Default for Instant { 267 | fn default() -> Instant { 268 | Self::now() 269 | } 270 | } 271 | 272 | impl Sub for Instant { 273 | type Output = Duration; 274 | 275 | #[inline] 276 | fn sub(self, other: Instant) -> Duration { 277 | Duration::from_u64(self.0.saturating_sub(other.0)) 278 | } 279 | } 280 | 281 | impl Sub for Instant { 282 | type Output = Instant; 283 | 284 | #[inline] 285 | fn sub(self, rhs: Duration) -> Instant { 286 | Instant(self.0 - rhs.as_u64()) 287 | } 288 | } 289 | 290 | impl SubAssign for Instant { 291 | #[inline] 292 | fn sub_assign(&mut self, rhs: Duration) { 293 | *self = *self - rhs; 294 | } 295 | } 296 | 297 | impl Add for Instant { 298 | type Output = Instant; 299 | 300 | #[inline] 301 | fn add(self, rhs: Duration) -> Instant { 302 | Instant(self.0 + rhs.as_u64()) 303 | } 304 | } 305 | 306 | impl AddAssign for Instant { 307 | #[inline] 308 | fn add_assign(&mut self, rhs: Duration) { 309 | *self = *self + rhs; 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A crate to make time measurements that focuses on speed. 2 | //! 3 | //! This crate is a partial replacement for the `Time` and `Duration` structures 4 | //! from the standard library, with the following differences: 5 | //! 6 | //! * Speed is privileged over accuracy. In particular, `CLOCK_MONOTONIC_COARSE` 7 | //! is used to retrieve the clock value on Linux systems, and transformations avoid 8 | //! operations that can be slow on non-Intel systems. 9 | //! * The number of system calls can be kept to a minimum. The "most recent 10 | //! timestamp" is always kept in memory. 11 | //! It can be read with just a load operation, and can be 12 | //! updated only as frequently as necessary. 13 | //! 14 | //! # Installation 15 | //! 16 | //! `coarsetime` is available on [crates.io](https://crates.io/crates/coarsetime) and works on 17 | //! Rust stable, beta, and nightly. 18 | //! 19 | //! Windows and Unix-like systems are supported. 20 | 21 | #![allow(clippy::trivially_copy_pass_by_ref)] 22 | 23 | mod clock; 24 | mod duration; 25 | mod helpers; 26 | mod instant; 27 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 28 | mod updater; 29 | 30 | #[cfg(test)] 31 | mod tests; 32 | 33 | pub use self::clock::*; 34 | pub use self::duration::*; 35 | pub use self::instant::*; 36 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 37 | pub use self::updater::*; 38 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::thread::sleep; 2 | use std::time; 3 | 4 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 5 | use super::Updater; 6 | use super::{Clock, Duration, Instant}; 7 | 8 | #[test] 9 | fn tests() { 10 | let ts = Instant::now(); 11 | let d = Duration::from_secs(2); 12 | sleep(time::Duration::new(3, 0)); 13 | let elapsed = ts.elapsed().as_secs(); 14 | println!("Elapsed: {elapsed} secs"); 15 | assert!(elapsed >= 2); 16 | assert!(elapsed < 100); 17 | assert!(ts.elapsed_since_recent() > d); 18 | 19 | let ts = Instant::now(); 20 | sleep(time::Duration::new(1, 0)); 21 | assert_eq!(Instant::recent(), ts); 22 | Instant::update(); 23 | assert!(Instant::recent() > ts); 24 | 25 | let clock_now = Clock::recent_since_epoch(); 26 | sleep(time::Duration::new(1, 0)); 27 | assert_eq!(Clock::recent_since_epoch(), clock_now); 28 | assert!(Clock::now_since_epoch() > clock_now); 29 | 30 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 31 | tests_updater(); 32 | } 33 | 34 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 35 | #[test] 36 | fn tests_updater() { 37 | let updater = Updater::new(250) 38 | .start() 39 | .expect("Unable to start a background updater"); 40 | let ts = Instant::recent(); 41 | let clock_recent = Clock::recent_since_epoch(); 42 | sleep(time::Duration::new(2, 0)); 43 | assert!(Clock::recent_since_epoch() > clock_recent); 44 | assert!(Instant::recent() != ts); 45 | updater.stop().unwrap(); 46 | let clock_recent = Clock::recent_since_epoch(); 47 | sleep(time::Duration::new(1, 0)); 48 | assert_eq!(Clock::recent_since_epoch(), clock_recent); 49 | } 50 | 51 | #[test] 52 | fn tests_duration() { 53 | let duration = Duration::from_days(1000); 54 | assert_eq!(duration.as_days(), 1000); 55 | } -------------------------------------------------------------------------------- /src/updater.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::sync::atomic::{AtomicBool, Ordering}; 3 | use std::sync::Arc; 4 | use std::thread; 5 | use std::time; 6 | 7 | use super::clock::*; 8 | use super::instant::*; 9 | 10 | /// A service to periodically call `Instant::update()` 11 | #[derive(Debug)] 12 | pub struct Updater { 13 | period: time::Duration, 14 | running: Arc, 15 | th: Option>, 16 | } 17 | 18 | impl Updater { 19 | /// Spawns a background task to call `Instant::update()` periodically 20 | pub fn start(mut self) -> Result { 21 | let period = self.period; 22 | let running = self.running.clone(); 23 | running.store(true, Ordering::Relaxed); 24 | let th: thread::JoinHandle<()> = thread::Builder::new() 25 | .name("coarsetime".to_string()) 26 | .spawn(move || { 27 | while running.load(Ordering::Relaxed) { 28 | thread::sleep(period); 29 | Instant::update(); 30 | Clock::update(); 31 | } 32 | })?; 33 | self.th = Some(th); 34 | Instant::update(); 35 | Clock::update(); 36 | Ok(self) 37 | } 38 | 39 | /// Stops the periodic updates 40 | pub fn stop(mut self) -> Result<(), io::Error> { 41 | self.running.store(false, Ordering::Relaxed); 42 | self.th 43 | .take() 44 | .expect("updater is not running") 45 | .join() 46 | .map_err(|_| { 47 | io::Error::new(io::ErrorKind::Other, "failed to properly stop the updater") 48 | }) 49 | } 50 | 51 | /// Creates a new `Updater` with the specified update period, in 52 | /// milliseconds. 53 | pub fn new(period_millis: u64) -> Updater { 54 | Updater { 55 | period: time::Duration::from_millis(period_millis), 56 | running: Arc::new(AtomicBool::new(false)), 57 | th: None, 58 | } 59 | } 60 | } 61 | --------------------------------------------------------------------------------