From 3bdc10cb83f8c0a7f6c721719846b34ac3b4828f Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 27 Feb 2023 09:09:03 -0800 Subject: [PATCH 1/4] time: Improve `Instant::now()` perf with test-util The test-util feature flag is only intended to be use with tests. However, it is possible to accidentally enable it in release mode. This patch reduces the overhead of `Instant::now()` when the `test-util` feature flag is enabled but `time::pause()` is not called. The optimization is implemented by adding a static atomic flag that tracks if `time::pause()` has ever been called. In `Instant::now()`, the atomic flag is first checked before the thread-local and mutex are accessed. --- benches/Cargo.toml | 8 ++++++++ benches/time_now.rs | 28 ++++++++++++++++++++++++++++ tokio/src/time/clock.rs | 15 +++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 benches/time_now.rs diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 10c32de0f3f..7b5d2e10501 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -4,6 +4,9 @@ version = "0.0.0" publish = false edition = "2018" +[features] +test-util = ["tokio/test-util"] + [dependencies] tokio = { version = "1.5.0", path = "../tokio", features = ["full"] } bencher = "0.1.5" @@ -67,3 +70,8 @@ harness = false name = "copy" path = "copy.rs" harness = false + +[[bench]] +name = "time_now" +path = "time_now.rs" +harness = false diff --git a/benches/time_now.rs b/benches/time_now.rs new file mode 100644 index 00000000000..ada48bbc0c0 --- /dev/null +++ b/benches/time_now.rs @@ -0,0 +1,28 @@ +//! Benchmark spawning a task onto the basic and threaded Tokio executors. +//! This essentially measure the time to enqueue a task in the local and remote +//! case. + +#[macro_use] +extern crate bencher; + +use bencher::{black_box, Bencher}; + +fn time_now_current_thread(bench: &mut Bencher) { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap(); + + bench.iter(|| { + rt.block_on(async { + black_box(tokio::time::Instant::now()); + }) + }) +} + +bencher::benchmark_group!( + time_now, + time_now_current_thread, +); + +bencher::benchmark_main!(time_now); diff --git a/tokio/src/time/clock.rs b/tokio/src/time/clock.rs index 1e273554ec7..4111511d32c 100644 --- a/tokio/src/time/clock.rs +++ b/tokio/src/time/clock.rs @@ -30,6 +30,7 @@ cfg_not_test_util! { cfg_test_util! { use crate::time::{Duration, Instant}; use crate::loom::sync::Mutex; + use crate::loom::sync::atomic::{AtomicBool, Ordering}; cfg_rt! { #[track_caller] @@ -65,6 +66,13 @@ cfg_test_util! { inner: Mutex, } + // Used to track if the clock was ever paused. This is an optimization to + // avoid touching the mutex if `test-util` was accidentally enabled in + // release mode. + // + // A static is used so we can avoid accessing the thread-local as well. + static DID_PAUSE_CLOCK: AtomicBool = AtomicBool::new(false); + #[derive(Debug)] struct Inner { /// True if the ability to pause time is enabled. @@ -199,6 +207,10 @@ cfg_test_util! { /// Returns the current instant, factoring in frozen time. pub(crate) fn now() -> Instant { + if !DID_PAUSE_CLOCK.load(Ordering::Acquire) { + return Instant::from_std(std::time::Instant::now()); + } + with_clock(|maybe_clock| { Ok(if let Some(clock) = maybe_clock { clock.now() @@ -241,6 +253,9 @@ cfg_test_util! { This is the default Runtime used by `#[tokio::test]."); } + // Track that we paused the clock + DID_PAUSE_CLOCK.store(true, Ordering::Release); + let elapsed = match inner.unfrozen.as_ref() { Some(v) => v.elapsed(), None => return Err("time is already frozen") From 729215b9599218a47b686d82cf9cd04b0d91afab Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 27 Feb 2023 09:39:30 -0800 Subject: [PATCH 2/4] Fix CI --- benches/time_now.rs | 5 +---- tokio/src/time/clock.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/benches/time_now.rs b/benches/time_now.rs index ada48bbc0c0..8146285691f 100644 --- a/benches/time_now.rs +++ b/benches/time_now.rs @@ -20,9 +20,6 @@ fn time_now_current_thread(bench: &mut Bencher) { }) } -bencher::benchmark_group!( - time_now, - time_now_current_thread, -); +bencher::benchmark_group!(time_now, time_now_current_thread,); bencher::benchmark_main!(time_now); diff --git a/tokio/src/time/clock.rs b/tokio/src/time/clock.rs index 4111511d32c..091cf4b19d7 100644 --- a/tokio/src/time/clock.rs +++ b/tokio/src/time/clock.rs @@ -30,7 +30,8 @@ cfg_not_test_util! { cfg_test_util! { use crate::time::{Duration, Instant}; use crate::loom::sync::Mutex; - use crate::loom::sync::atomic::{AtomicBool, Ordering}; + use crate::loom::sync::atomic::Ordering; + use std::sync::atomic::AtomicBool as StdAtomicBool; cfg_rt! { #[track_caller] @@ -70,8 +71,10 @@ cfg_test_util! { // avoid touching the mutex if `test-util` was accidentally enabled in // release mode. // - // A static is used so we can avoid accessing the thread-local as well. - static DID_PAUSE_CLOCK: AtomicBool = AtomicBool::new(false); + // A static is used so we can avoid accessing the thread-local as well. The + // `std` AtomicBool is used directly because loom does not support static + // atomics. + static DID_PAUSE_CLOCK: StdAtomicBool = StdAtomicBool::new(false); #[derive(Debug)] struct Inner { From ea47fec5aba08666b45b0c0fcaf2891f636408ed Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 27 Feb 2023 09:52:03 -0800 Subject: [PATCH 3/4] add test for `start_paused` --- tokio/tests/rt_time_start_paused.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tokio/tests/rt_time_start_paused.rs diff --git a/tokio/tests/rt_time_start_paused.rs b/tokio/tests/rt_time_start_paused.rs new file mode 100644 index 00000000000..75d2a3957de --- /dev/null +++ b/tokio/tests/rt_time_start_paused.rs @@ -0,0 +1,14 @@ +#![cfg(all(feature = "full"))] + +use tokio::time::{Duration, Instant}; + +#[tokio::test(start_paused = true)] +async fn test_start_paused() { + let now = Instant::now(); + + // Pause a few times w/ std sleep and ensure `now` stays the same + for _ in 0..5 { + std::thread::sleep(Duration::from_millis(1)); + assert_eq!(now, Instant::now()); + } +} \ No newline at end of file From 19d75aa780970b4d3533127185daaf1181dbbe22 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 27 Feb 2023 09:55:28 -0800 Subject: [PATCH 4/4] fmt --- tokio/tests/rt_time_start_paused.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/tests/rt_time_start_paused.rs b/tokio/tests/rt_time_start_paused.rs index 75d2a3957de..283f4748a8a 100644 --- a/tokio/tests/rt_time_start_paused.rs +++ b/tokio/tests/rt_time_start_paused.rs @@ -11,4 +11,4 @@ async fn test_start_paused() { std::thread::sleep(Duration::from_millis(1)); assert_eq!(now, Instant::now()); } -} \ No newline at end of file +}