├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── criterion.rs ├── rustfmt.toml └── src ├── instant.rs ├── lib.rs └── tsc_now.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | runs-on: ubuntu-24.04 15 | env: 16 | RUST_BACKTRACE: 1 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: Swatinem/rust-cache@v2 20 | - name: Set up toolchains 21 | uses: dtolnay/rust-toolchain@master 22 | with: 23 | toolchain: nightly 24 | components: rustfmt, clippy 25 | - name: Check format 26 | run: cargo +nightly fmt --all -- --check 27 | - name: Clippy 28 | run: cargo +nightly clippy --workspace --all-targets --all-features -- -D warnings 29 | 30 | build: 31 | runs-on: ${{ matrix.os }} 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | os: [ ubuntu-24.04, macos-14, windows-2022 ] 36 | features: [ "atomic", "atomic,fallback-coarse" ] 37 | env: 38 | RUST_BACKTRACE: 1 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: Swatinem/rust-cache@v2 42 | - name: Set up toolchains 43 | uses: dtolnay/rust-toolchain@master 44 | with: 45 | toolchain: stable 46 | components: rustfmt, clippy 47 | - name: Build 48 | run: cargo build --workspace --all-targets --features ${{ matrix.features }} 49 | - name: Run tests 50 | run: cargo test --workspace --all-targets --features ${{ matrix.features }} -- --nocapture 51 | - name: Run benches 52 | run: cargo bench --workspace --all-targets --features ${{ matrix.features }} 53 | 54 | build-wasm: 55 | runs-on: ubuntu-24.04 56 | env: 57 | RUST_BACKTRACE: 1 58 | RUSTFLAGS: --cfg getrandom_backend="wasm_js" 59 | steps: 60 | - uses: actions/checkout@v4 61 | - name: Set up toolchains 62 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 63 | - run: wasm-pack test --node 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fastant" 3 | version = "0.1.10" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "A drop-in replacement for `std::time::Instant` that measures time with high performance and high accuracy powered by Time Stamp Counter (TSC)." 7 | homepage = "https://github.com/fast/fastant" 8 | repository = "https://github.com/fast/fastant" 9 | documentation = "https://docs.rs/fastant" 10 | readme = "README.md" 11 | keywords = ["TSC", "clock", "rdtsc", "timing", "nanosecond"] 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | coarsetime = { version = "0.1", optional = true } 19 | small_ctor = { version = "0.1.2" } 20 | web-time = { version = "1.1.0" } 21 | 22 | [features] 23 | atomic = [] 24 | fallback-coarse = ["coarsetime"] 25 | 26 | [dev-dependencies] 27 | criterion = { version = "0.5", default-features = false, features = [ 28 | "plotters", 29 | "cargo_bench_support", 30 | ] } 31 | quanta = { version = "0.12.5" } 32 | rand = { version = "0.9.0" } 33 | wasm-bindgen-test = { version = "0.3.50" } 34 | 35 | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] 36 | getrandom = { version = "0.3", features = ["wasm_js"] } 37 | 38 | [[bench]] 39 | name = "criterion" 40 | harness = false 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fastant 2 | 3 | A drop-in replacement for [`std::time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html) that measures time with high performance and high accuracy powered by [Time Stamp Counter (TSC)](https://en.wikipedia.org/wiki/Time_Stamp_Counter). 4 | 5 | [![Actions Status](https://github.com/fast/fastant/workflows/CI/badge.svg)](https://github.com/fast/fastant/actions) 6 | [![Documentation](https://docs.rs/fastant/badge.svg)](https://docs.rs/fastant/) 7 | [![Crates.io](https://img.shields.io/crates/v/fastant.svg)](https://crates.io/crates/fastant) 8 | [![LICENSE](https://img.shields.io/github/license/fast/fastant.svg)](LICENSE) 9 | 10 | ## Usage 11 | 12 | ```toml 13 | [dependencies] 14 | fastant = "0.1" 15 | ``` 16 | 17 | ```rust 18 | fn main() { 19 | let start = fastant::Instant::now(); 20 | let duration: std::time::Duration = start.elapsed(); 21 | } 22 | ``` 23 | 24 | ## Motivation 25 | 26 | This library is used by a high performance tracing library [`fastrace`](https://github.com/fast/fastrace). The main purpose is to use [Time Stamp Counter (TSC)](https://en.wikipedia.org/wiki/Time_Stamp_Counter) on x86 processors to measure time at high speed without losing much accuracy. 27 | 28 | ## Platform Support 29 | 30 | Currently, only the Linux on `x86` or `x86_64` is backed by Time Stamp Counter (TSC). On other platforms, Fastant falls back to `std::time`. If TSC is unstable, it will also fall back to `std::time`. 31 | 32 | If speed is privileged over accuracy when fallback occurs, you can use `fallback-coarse` feature to use coarse time: 33 | 34 | ```toml 35 | [dependencies] 36 | fastant = { version = "0.1", features = ["fallback-coarse"] } 37 | ``` 38 | -------------------------------------------------------------------------------- /benches/criterion.rs: -------------------------------------------------------------------------------- 1 | use criterion::black_box; 2 | use criterion::criterion_group; 3 | use criterion::criterion_main; 4 | use criterion::Criterion; 5 | 6 | fn bench_now(c: &mut Criterion) { 7 | // The first call will take some time for calibration 8 | quanta::Instant::now(); 9 | 10 | let mut group = c.benchmark_group("Instant::now()"); 11 | group.bench_function("fastant", |b| { 12 | b.iter(fastant::Instant::now); 13 | }); 14 | group.bench_function("quanta", |b| { 15 | b.iter(quanta::Instant::now); 16 | }); 17 | group.bench_function("std", |b| { 18 | b.iter(std::time::Instant::now); 19 | }); 20 | group.finish(); 21 | } 22 | 23 | fn bench_anchor_new(c: &mut Criterion) { 24 | c.bench_function("fastant::Anchor::new()", |b| { 25 | b.iter(fastant::Anchor::new); 26 | }); 27 | } 28 | 29 | fn bench_as_unix_nanos(c: &mut Criterion) { 30 | let anchor = fastant::Anchor::new(); 31 | c.bench_function("fastant::Instant::as_unix_nanos()", |b| { 32 | b.iter(|| { 33 | black_box(fastant::Instant::now().as_unix_nanos(&anchor)); 34 | }); 35 | }); 36 | } 37 | 38 | criterion_group!(benches, bench_now, bench_anchor_new, bench_as_unix_nanos); 39 | criterion_main!(benches); 40 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | comment_width = 120 2 | edition = "2021" 3 | format_code_in_doc_comments = true 4 | group_imports = "StdExternalCrate" 5 | imports_granularity = "Item" 6 | normalize_comments = true 7 | overflow_delimited_expr = true 8 | reorder_imports = true 9 | trailing_comma = "Vertical" 10 | style_edition = "2021" 11 | where_single_line = true 12 | wrap_comments = true 13 | -------------------------------------------------------------------------------- /src/instant.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::ops::Add; 4 | use std::ops::AddAssign; 5 | use std::ops::Sub; 6 | use std::ops::SubAssign; 7 | use std::time::Duration; 8 | 9 | use web_time::SystemTime; 10 | use web_time::UNIX_EPOCH; 11 | 12 | /// A measurement of a monotonically non-decreasing clock. Similar to 13 | /// [`std::time::Instant`](std::time::Instant) but is faster and more 14 | /// accurate if TSC is available. 15 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 | #[repr(transparent)] 17 | pub struct Instant(u64); 18 | 19 | impl Instant { 20 | /// A default `Instant` that can be seen as a fixed but random moment. 21 | pub const ZERO: Instant = Instant(0); 22 | 23 | #[inline] 24 | /// Returns an instant corresponding to "now". 25 | /// 26 | /// # Examples 27 | /// 28 | /// ```rust 29 | /// use fastant::Instant; 30 | /// let now = Instant::now(); 31 | /// ``` 32 | pub fn now() -> Instant { 33 | Instant(crate::current_cycle()) 34 | } 35 | 36 | /// Returns the amount of time elapsed from another instant to this one, 37 | /// or zero duration if that instant is later than this one. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use std::thread::sleep; 43 | /// use std::time::Duration; 44 | /// 45 | /// use fastant::Instant; 46 | /// 47 | /// let now = Instant::now(); 48 | /// sleep(Duration::new(1, 0)); 49 | /// 50 | /// let new_now = Instant::now(); 51 | /// println!("{:?}", new_now.duration_since(now)); 52 | /// println!("{:?}", now.duration_since(new_now)); // 0ns 53 | /// ``` 54 | pub fn duration_since(&self, earlier: Instant) -> Duration { 55 | self.checked_duration_since(earlier).unwrap_or_default() 56 | } 57 | 58 | /// Returns the amount of time elapsed from another instant to this one, 59 | /// or None if that instant is later than this one. 60 | /// 61 | /// # Examples 62 | /// 63 | /// ``` 64 | /// use std::thread::sleep; 65 | /// use std::time::Duration; 66 | /// 67 | /// use fastant::Instant; 68 | /// 69 | /// let now = Instant::now(); 70 | /// sleep(Duration::new(1, 0)); 71 | /// 72 | /// let new_now = Instant::now(); 73 | /// println!("{:?}", new_now.checked_duration_since(now)); 74 | /// println!("{:?}", now.checked_duration_since(new_now)); // None 75 | /// ``` 76 | pub fn checked_duration_since(&self, earlier: Instant) -> Option { 77 | Some(Duration::from_nanos( 78 | (self.0.checked_sub(earlier.0)? as f64 * crate::nanos_per_cycle()) as u64, 79 | )) 80 | } 81 | 82 | /// Returns the amount of time elapsed from another instant to this one, 83 | /// or zero duration if that instant is later than this one. 84 | /// 85 | /// # Examples 86 | /// 87 | /// ``` 88 | /// use std::thread::sleep; 89 | /// use std::time::Duration; 90 | /// 91 | /// use fastant::Instant; 92 | /// 93 | /// let now = Instant::now(); 94 | /// sleep(Duration::new(1, 0)); 95 | /// 96 | /// let new_now = Instant::now(); 97 | /// println!("{:?}", new_now.saturating_duration_since(now)); 98 | /// println!("{:?}", now.saturating_duration_since(new_now)); // 0ns 99 | /// ``` 100 | pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { 101 | self.checked_duration_since(earlier).unwrap_or_default() 102 | } 103 | 104 | /// Returns the amount of time elapsed since this instant was created. 105 | /// 106 | /// # Examples 107 | /// 108 | /// ``` 109 | /// use std::thread::sleep; 110 | /// use std::time::Duration; 111 | /// 112 | /// use fastant::Instant; 113 | /// 114 | /// let instant = Instant::now(); 115 | /// let three_secs = Duration::from_secs(3); 116 | /// sleep(three_secs); 117 | /// assert!(instant.elapsed() >= three_secs); 118 | /// ``` 119 | #[inline] 120 | pub fn elapsed(&self) -> Duration { 121 | Instant::now() - *self 122 | } 123 | 124 | /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as 125 | /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` 126 | /// otherwise. 127 | pub fn checked_add(&self, duration: Duration) -> Option { 128 | self.0 129 | .checked_add((duration.as_nanos() as u64 as f64 / crate::nanos_per_cycle()) as u64) 130 | .map(Instant) 131 | } 132 | 133 | /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as 134 | /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` 135 | /// otherwise. 136 | pub fn checked_sub(&self, duration: Duration) -> Option { 137 | self.0 138 | .checked_sub((duration.as_nanos() as u64 as f64 / crate::nanos_per_cycle()) as u64) 139 | .map(Instant) 140 | } 141 | 142 | /// Convert internal clocking counter into a UNIX timestamp represented as the 143 | /// nanoseconds elapsed from [UNIX_EPOCH](UNIX_EPOCH). 144 | /// 145 | /// [`Anchor`](Anchor) contains the necessary calibration data for conversion. 146 | /// Typically, initializing an [`Anchor`](Anchor) takes about 50 nanoseconds, so 147 | /// try to reuse it for a batch of `Instant`. 148 | /// 149 | /// # Examples 150 | /// 151 | /// ``` 152 | /// use std::time::UNIX_EPOCH; 153 | /// 154 | /// use fastant::Anchor; 155 | /// use fastant::Instant; 156 | /// 157 | /// let anchor = Anchor::new(); 158 | /// let instant = Instant::now(); 159 | /// 160 | /// let expected = UNIX_EPOCH.elapsed().unwrap().as_nanos(); 161 | /// assert!((instant.as_unix_nanos(&anchor) as i64 - expected as i64).abs() < 1_000_000); 162 | /// ``` 163 | pub fn as_unix_nanos(&self, anchor: &Anchor) -> u64 { 164 | if self.0 > anchor.cycle { 165 | let forward_ns = ((self.0 - anchor.cycle) as f64 * crate::nanos_per_cycle()) as u64; 166 | anchor.unix_time_ns + forward_ns 167 | } else { 168 | let backward_ns = ((anchor.cycle - self.0) as f64 * crate::nanos_per_cycle()) as u64; 169 | anchor.unix_time_ns - backward_ns 170 | } 171 | } 172 | } 173 | 174 | impl Add for Instant { 175 | type Output = Instant; 176 | 177 | fn add(self, other: Duration) -> Instant { 178 | self.checked_add(other) 179 | .expect("overflow when adding duration to instant") 180 | } 181 | } 182 | 183 | impl AddAssign for Instant { 184 | fn add_assign(&mut self, other: Duration) { 185 | *self = *self + other; 186 | } 187 | } 188 | 189 | impl Sub for Instant { 190 | type Output = Instant; 191 | 192 | fn sub(self, other: Duration) -> Instant { 193 | self.checked_sub(other) 194 | .expect("overflow when subtracting duration from instant") 195 | } 196 | } 197 | 198 | impl SubAssign for Instant { 199 | fn sub_assign(&mut self, other: Duration) { 200 | *self = *self - other; 201 | } 202 | } 203 | 204 | impl Sub for Instant { 205 | type Output = Duration; 206 | 207 | /// Returns the amount of time elapsed from another instant to this one, 208 | /// or zero duration if that instant is later than this one. 209 | fn sub(self, other: Instant) -> Duration { 210 | self.duration_since(other) 211 | } 212 | } 213 | 214 | impl std::fmt::Debug for Instant { 215 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 216 | self.0.fmt(f) 217 | } 218 | } 219 | 220 | /// An anchor which can be used to convert internal clocking counter into system timestamp. 221 | /// 222 | /// **[See also the `Instant::as_unix_nanos()`](Instant::as_unix_nanos).** 223 | #[derive(Copy, Clone)] 224 | pub struct Anchor { 225 | unix_time_ns: u64, 226 | cycle: u64, 227 | } 228 | 229 | impl Default for Anchor { 230 | fn default() -> Self { 231 | Self::new() 232 | } 233 | } 234 | 235 | impl Anchor { 236 | #[inline] 237 | pub fn new() -> Anchor { 238 | let unix_time_ns = SystemTime::now() 239 | .duration_since(UNIX_EPOCH) 240 | .expect("unexpected time drift") 241 | .as_nanos() as u64; 242 | Anchor { 243 | unix_time_ns, 244 | cycle: crate::current_cycle(), 245 | } 246 | } 247 | } 248 | 249 | #[cfg(all(feature = "atomic", target_has_atomic = "64"))] 250 | #[cfg_attr(docsrs, doc(cfg(all(feature = "atomic", target_has_atomic = "64"))))] 251 | mod atomic { 252 | use std::sync::atomic::AtomicU64; 253 | use std::sync::atomic::Ordering; 254 | 255 | use super::Instant; 256 | 257 | /// Atomic variant of [`Instant`]. 258 | #[derive(Debug)] 259 | #[repr(transparent)] 260 | pub struct Atomic(AtomicU64); 261 | 262 | impl Atomic { 263 | /// Maximum with the current value. 264 | /// 265 | /// Finds the maximum of the current value and the argument `val`, and 266 | /// sets the new value to the result. 267 | /// 268 | /// Returns the previous value. 269 | /// 270 | /// `fetch_max` takes an [`Ordering`] argument which describes the memory ordering 271 | /// of this operation. All ordering modes are possible. Note that using 272 | /// [`Acquire`] makes the store part of this operation [`Relaxed`], and 273 | /// using [`Release`] makes the load part [`Relaxed`]. 274 | /// 275 | /// **Note**: This method is only available on platforms that support atomic operations on 276 | /// `[u64]`. 277 | #[inline] 278 | pub fn fetch_max(&self, val: Instant, order: Ordering) -> Instant { 279 | Instant(self.0.fetch_max(val.0, order)) 280 | } 281 | 282 | /// Minimum with the current value. 283 | /// 284 | /// Finds the minimum of the current value and the argument `val`, and 285 | /// sets the new value to the result. 286 | /// 287 | /// Returns the previous value. 288 | /// 289 | /// `fetch_min` takes an [`Ordering`] argument which describes the memory ordering 290 | /// of this operation. All ordering modes are possible. Note that using 291 | /// [`Acquire`] makes the store part of this operation [`Relaxed`], and 292 | /// using [`Release`] makes the load part [`Relaxed`]. 293 | /// 294 | /// **Note**: This method is only available on platforms that support atomic operations on 295 | /// `[u64]`. 296 | #[inline] 297 | pub fn fetch_min(&self, val: Instant, order: Ordering) -> Instant { 298 | Instant(self.0.fetch_min(val.0, order)) 299 | } 300 | 301 | /// Consumes the atomic and returns the contained [`Instant`]. 302 | /// 303 | /// This is safe because passing `self` by value guarantees that no other threads are 304 | /// concurrently accessing the atomic data. 305 | #[inline] 306 | pub fn into_instant(self) -> Instant { 307 | Instant(self.0.into_inner()) 308 | } 309 | 310 | /// Loads a value from the [`Atomic`]. 311 | /// 312 | /// `load` takes an [`Ordering`] argument which describes the memory ordering of this 313 | /// operation. Possible values are [`SeqCst`], [`Acquire`] and [`Relaxed`]. 314 | /// 315 | /// # Panics 316 | /// 317 | /// Panics if `order` is [`Release`] or [`AcqRel`]. 318 | #[inline] 319 | pub fn load(&self, order: Ordering) -> Instant { 320 | Instant(self.0.load(order)) 321 | } 322 | 323 | /// Creates a new [`Atomic`]. 324 | #[inline] 325 | pub fn new(v: Instant) -> Self { 326 | Self(AtomicU64::new(v.0)) 327 | } 328 | 329 | /// Stores a value into the [`Atomic`]. 330 | /// 331 | /// `store` takes an [`Ordering`] argument which describes the memory ordering of this 332 | /// operation. Possible values are [`SeqCst`], [`Release`] and [`Relaxed`]. 333 | /// 334 | /// # Panics 335 | /// 336 | /// Panics if `order` is [`Acquire`] or [`AcqRel`]. 337 | #[inline] 338 | pub fn store(&self, val: Instant, order: Ordering) { 339 | self.0.store(val.0, order) 340 | } 341 | 342 | /// Stores a value into the [`Atomic`], returning the previous value. 343 | /// 344 | /// `swap` takes an [`Ordering`] argument which describes the memory ordering 345 | /// of this operation. All ordering modes are possible. Note that using 346 | /// [`Acquire`] makes the store part of this operation [`Relaxed`], and 347 | /// using [`Release`] makes the load part [`Relaxed`]. 348 | /// 349 | /// **Note**: This method is only available on platforms that support atomic operations on 350 | /// `u64` 351 | #[inline] 352 | pub fn swap(&self, val: Instant, order: Ordering) -> Instant { 353 | Instant(self.0.swap(val.0, order)) 354 | } 355 | } 356 | 357 | impl From for Atomic { 358 | #[inline] 359 | fn from(instant: Instant) -> Self { 360 | Self::new(instant) 361 | } 362 | } 363 | } 364 | 365 | #[cfg(all(feature = "atomic", target_has_atomic = "64"))] 366 | #[cfg_attr(docsrs, doc(cfg(all(feature = "atomic", target_has_atomic = "64"))))] 367 | pub use atomic::Atomic; 368 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | //! A drop-in replacement for [`std::time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html) 4 | //! that measures time with high performance and high accuracy powered by [Time Stamp Counter (TSC)](https://en.wikipedia.org/wiki/Time_Stamp_Counter). 5 | //! 6 | //! ## Example 7 | //! 8 | //! ```rust 9 | //! let start = fastant::Instant::now(); 10 | //! let duration: std::time::Duration = start.elapsed(); 11 | //! ``` 12 | //! 13 | //! ## Platform Support 14 | //! 15 | //! Currently, only the Linux on `x86` or `x86_64` is backed by Time Stamp Counter (TSC). 16 | //! On other platforms, `fastant` falls back to coarse time. 17 | //! 18 | //! ## Calibration 19 | //! 20 | //! Time Stamp Counter (TSC) doesn't necessarily tick in constant speed and even doesn't synchronize 21 | //! across CPU cores. The calibration detects the TSC deviation and calculates the correction 22 | //! factors with the assistance of a source wall clock. Once the deviation is beyond a crazy 23 | //! threshold, the calibration will fail, and then we will fall back to coarse time. 24 | //! 25 | //! This calibration is stored globally and reused. In order to start the calibration before any 26 | //! call to `fastant` as to make sure that the time spent on `fastant` is constant, we link the 27 | //! calibration into application's initialization linker section, so it'll get executed once the 28 | //! process starts. 29 | //! 30 | //! **[See also the `Instant` type](Instant).** 31 | 32 | #![cfg_attr(docsrs, feature(doc_cfg))] 33 | 34 | mod instant; 35 | #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))] 36 | mod tsc_now; 37 | 38 | pub use instant::Anchor; 39 | #[cfg(all(feature = "atomic", target_has_atomic = "64"))] 40 | #[cfg_attr(docsrs, doc(cfg(all(feature = "atomic", target_has_atomic = "64"))))] 41 | pub use instant::Atomic; 42 | pub use instant::Instant; 43 | 44 | /// Return `true` if the current platform supports Time Stamp Counter (TSC), 45 | /// and the calibration has succeeded. 46 | /// 47 | /// The result is always the same during the lifetime of the application process. 48 | #[inline] 49 | pub fn is_tsc_available() -> bool { 50 | #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))] 51 | { 52 | tsc_now::is_tsc_available() 53 | } 54 | #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))] 55 | { 56 | false 57 | } 58 | } 59 | 60 | #[inline] 61 | pub(crate) fn current_cycle() -> u64 { 62 | #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))] 63 | { 64 | if tsc_now::is_tsc_available() { 65 | tsc_now::current_cycle() 66 | } else { 67 | current_cycle_fallback() 68 | } 69 | } 70 | #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))] 71 | { 72 | current_cycle_fallback() 73 | } 74 | } 75 | 76 | #[cfg(not(feature = "fallback-coarse"))] 77 | pub(crate) fn current_cycle_fallback() -> u64 { 78 | web_time::SystemTime::now() 79 | .duration_since(web_time::UNIX_EPOCH) 80 | .map(|d| d.as_nanos() as u64) 81 | .unwrap_or(0) 82 | } 83 | 84 | #[cfg(feature = "fallback-coarse")] 85 | pub(crate) fn current_cycle_fallback() -> u64 { 86 | let coarse = coarsetime::Instant::now_without_cache_update(); 87 | coarsetime::Duration::from_ticks(coarse.as_ticks()).as_nanos() 88 | } 89 | 90 | #[inline] 91 | pub(crate) fn nanos_per_cycle() -> f64 { 92 | #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))] 93 | { 94 | tsc_now::nanos_per_cycle() 95 | } 96 | #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))] 97 | { 98 | 1.0 99 | } 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use std::time::Duration; 105 | use std::time::Instant as StdInstant; 106 | 107 | use rand::Rng; 108 | use wasm_bindgen_test::wasm_bindgen_test; 109 | 110 | use super::*; 111 | 112 | #[test] 113 | #[wasm_bindgen_test] 114 | fn test_is_tsc_available() { 115 | let _ = is_tsc_available(); 116 | } 117 | 118 | #[test] 119 | #[wasm_bindgen_test] 120 | fn test_monotonic() { 121 | let mut prev = 0; 122 | for _ in 0..10000 { 123 | let cur = current_cycle(); 124 | assert!(cur >= prev); 125 | prev = cur; 126 | } 127 | } 128 | 129 | #[test] 130 | #[wasm_bindgen_test] 131 | fn test_nanos_per_cycle() { 132 | let _ = nanos_per_cycle(); 133 | } 134 | 135 | #[test] 136 | #[wasm_bindgen_test] 137 | fn test_unix_time() { 138 | let now = Instant::now(); 139 | let anchor = Anchor::new(); 140 | let unix_nanos = now.as_unix_nanos(&anchor); 141 | assert!(unix_nanos > 0); 142 | } 143 | 144 | #[test] 145 | fn test_duration() { 146 | let mut rng = rand::rng(); 147 | for _ in 0..10 { 148 | let instant = Instant::now(); 149 | let std_instant = StdInstant::now(); 150 | std::thread::sleep(Duration::from_millis(rng.random_range(100..500))); 151 | let check = move || { 152 | let duration_ns_fastant = instant.elapsed(); 153 | let duration_ns_std = std_instant.elapsed(); 154 | 155 | #[cfg(target_os = "windows")] 156 | let expect_max_delta_ns = 40_000_000; 157 | #[cfg(not(target_os = "windows"))] 158 | let expect_max_delta_ns = 5_000_000; 159 | 160 | let real_delta = (duration_ns_std.as_nanos() as i128 161 | - duration_ns_fastant.as_nanos() as i128) 162 | .abs(); 163 | assert!( 164 | real_delta < expect_max_delta_ns, 165 | "real delta: {}", 166 | real_delta 167 | ); 168 | }; 169 | check(); 170 | std::thread::spawn(check) 171 | .join() 172 | .expect("failed to join thread"); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/tsc_now.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | //! This module will be compiled when it's either linux_x86 or linux_x86_64. 4 | 5 | use std::cell::UnsafeCell; 6 | use std::fs::read_to_string; 7 | use std::io::ErrorKind; 8 | use std::time::Instant; 9 | 10 | static TSC_STATE: TSCState = TSCState { 11 | is_tsc_available: UnsafeCell::new(false), 12 | tsc_level: UnsafeCell::new(TSCLevel::Unstable), 13 | nanos_per_cycle: UnsafeCell::new(1.0), 14 | }; 15 | 16 | struct TSCState { 17 | is_tsc_available: UnsafeCell, 18 | tsc_level: UnsafeCell, 19 | nanos_per_cycle: UnsafeCell, 20 | } 21 | 22 | unsafe impl Sync for TSCState {} 23 | 24 | #[small_ctor::ctor] 25 | unsafe fn init() { 26 | let tsc_level = TSCLevel::get(); 27 | let is_tsc_available = match &tsc_level { 28 | TSCLevel::Stable { .. } => true, 29 | TSCLevel::Unstable => false, 30 | }; 31 | if is_tsc_available { 32 | *TSC_STATE.nanos_per_cycle.get() = 1_000_000_000.0 / tsc_level.cycles_per_second() as f64; 33 | } 34 | *TSC_STATE.is_tsc_available.get() = is_tsc_available; 35 | *TSC_STATE.tsc_level.get() = tsc_level; 36 | std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst); 37 | } 38 | 39 | #[inline] 40 | pub(crate) fn is_tsc_available() -> bool { 41 | unsafe { *TSC_STATE.is_tsc_available.get() } 42 | } 43 | 44 | #[inline] 45 | pub(crate) fn nanos_per_cycle() -> f64 { 46 | unsafe { *TSC_STATE.nanos_per_cycle.get() } 47 | } 48 | 49 | #[inline] 50 | pub(crate) fn current_cycle() -> u64 { 51 | match unsafe { &*TSC_STATE.tsc_level.get() } { 52 | TSCLevel::Stable { 53 | cycles_from_anchor, .. 54 | } => tsc().wrapping_sub(*cycles_from_anchor), 55 | TSCLevel::Unstable => panic!("tsc is unstable"), 56 | } 57 | } 58 | 59 | enum TSCLevel { 60 | Stable { 61 | cycles_per_second: u64, 62 | cycles_from_anchor: u64, 63 | }, 64 | Unstable, 65 | } 66 | 67 | impl TSCLevel { 68 | fn get() -> TSCLevel { 69 | if !is_tsc_stable() { 70 | return TSCLevel::Unstable; 71 | } 72 | 73 | let anchor = Instant::now(); 74 | let (cps, cfa) = cycles_per_sec(anchor); 75 | TSCLevel::Stable { 76 | cycles_per_second: cps, 77 | cycles_from_anchor: cfa, 78 | } 79 | } 80 | 81 | #[inline] 82 | fn cycles_per_second(&self) -> u64 { 83 | match self { 84 | TSCLevel::Stable { 85 | cycles_per_second, .. 86 | } => *cycles_per_second, 87 | TSCLevel::Unstable => panic!("tsc is unstable"), 88 | } 89 | } 90 | } 91 | 92 | /// If linux kernel detected TSCs are sync between CPUs, we can 93 | /// rely on the result to say tsc is stable so that no need to 94 | /// sync TSCs by ourselves. 95 | fn is_tsc_stable() -> bool { 96 | has_invariant_tsc() || clock_source_has_tsc() 97 | } 98 | 99 | fn clock_source_has_tsc() -> bool { 100 | #[cfg(target_os = "linux")] 101 | { 102 | const CURRENT_CLOCKSOURCE: &str = 103 | "/sys/devices/system/clocksource/clocksource0/current_clocksource"; 104 | const AVAILABLE_CLOCKSOURCE: &str = 105 | "/sys/devices/system/clocksource/clocksource0/available_clocksource"; 106 | 107 | match read_to_string(CURRENT_CLOCKSOURCE) { 108 | Ok(content) => content.contains("tsc"), 109 | Err(e) if e.kind() == ErrorKind::NotFound => { 110 | // we only check `available_clocksource` iff `current_clocksource` not exists. 111 | read_to_string(AVAILABLE_CLOCKSOURCE) 112 | .map(|s| s.contains("tsc")) 113 | .unwrap_or(false) 114 | } 115 | Err(_) => false, 116 | } 117 | } 118 | 119 | #[cfg(not(target_os = "linux"))] 120 | false 121 | } 122 | 123 | /// Invariant TSC could make sure TSC got synced among multi CPUs. 124 | /// They will be reset at same time, and run in same frequency. 125 | /// But in some VM, the max Extended Function in CPUID is < 0x80000007, 126 | /// we should enable TSC if the system clock source is TSC. 127 | #[inline] 128 | fn has_invariant_tsc() -> bool { 129 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 130 | unsafe { 131 | use core::arch::x86_64::__cpuid; 132 | let cpuid_invariant_tsc_bts = 1 << 8; 133 | __cpuid(0x80000000).eax >= 0x80000007 134 | && __cpuid(0x80000007).edx & cpuid_invariant_tsc_bts != 0 135 | } 136 | 137 | #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] 138 | false 139 | } 140 | 141 | /// Returns (1) cycles per second and (2) cycles from anchor. 142 | /// The result of subtracting `cycles_from_anchor` from newly fetched TSC 143 | /// can be used to 144 | /// 1. readjust TSC to begin from zero 145 | /// 2. sync TSCs between all CPUs 146 | fn cycles_per_sec(anchor: Instant) -> (u64, u64) { 147 | let (cps, last_monotonic, last_tsc) = _cycles_per_sec(); 148 | let nanos_from_anchor = (last_monotonic - anchor).as_nanos(); 149 | let cycles_flied = cps as f64 * nanos_from_anchor as f64 / 1_000_000_000.0; 150 | let cycles_from_anchor = last_tsc - cycles_flied.ceil() as u64; 151 | 152 | (cps, cycles_from_anchor) 153 | } 154 | 155 | /// Returns (1) cycles per second, (2) last monotonic time and (3) associated tsc. 156 | fn _cycles_per_sec() -> (u64, Instant, u64) { 157 | let mut cycles_per_sec; 158 | let mut last_monotonic; 159 | let mut last_tsc; 160 | let mut old_cycles = 0.0; 161 | 162 | loop { 163 | let (t1, tsc1) = monotonic_with_tsc(); 164 | loop { 165 | let (t2, tsc2) = monotonic_with_tsc(); 166 | last_monotonic = t2; 167 | last_tsc = tsc2; 168 | let elapsed_nanos = (t2 - t1).as_nanos(); 169 | if elapsed_nanos > 10_000_000 { 170 | cycles_per_sec = (tsc2 - tsc1) as f64 * 1_000_000_000.0 / elapsed_nanos as f64; 171 | break; 172 | } 173 | } 174 | let delta = f64::abs(cycles_per_sec - old_cycles); 175 | if delta / cycles_per_sec < 0.00001 { 176 | break; 177 | } 178 | old_cycles = cycles_per_sec; 179 | } 180 | 181 | (cycles_per_sec.round() as u64, last_monotonic, last_tsc) 182 | } 183 | 184 | /// Try to get tsc and monotonic time at the same time. Due to 185 | /// get interrupted in half way may happen, they aren't guaranteed 186 | /// to represent the same instant. 187 | fn monotonic_with_tsc() -> (Instant, u64) { 188 | (Instant::now(), tsc()) 189 | } 190 | 191 | #[inline] 192 | fn tsc() -> u64 { 193 | #[cfg(target_arch = "x86")] 194 | use core::arch::x86::_rdtsc; 195 | #[cfg(target_arch = "x86_64")] 196 | use core::arch::x86_64::_rdtsc; 197 | 198 | unsafe { _rdtsc() } 199 | } 200 | --------------------------------------------------------------------------------