Skip to content

Commit

Permalink
time: Improve Instant::now() perf with test-util (#5513)
Browse files Browse the repository at this point in the history
The test-util feature flag is only intended to be used with tests.
However, it is possible to enable it in release mode accidentally. 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.
  • Loading branch information
carllerche committed Feb 27, 2023
1 parent 815d89a commit ee1c940
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 0 deletions.
8 changes: 8 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -67,3 +70,8 @@ harness = false
name = "copy"
path = "copy.rs"
harness = false

[[bench]]
name = "time_now"
path = "time_now.rs"
harness = false
25 changes: 25 additions & 0 deletions benches/time_now.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! 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);
18 changes: 18 additions & 0 deletions tokio/src/time/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ cfg_not_test_util! {
cfg_test_util! {
use crate::time::{Duration, Instant};
use crate::loom::sync::Mutex;
use crate::loom::sync::atomic::Ordering;
use std::sync::atomic::AtomicBool as StdAtomicBool;

cfg_rt! {
#[track_caller]
Expand Down Expand Up @@ -65,6 +67,15 @@ cfg_test_util! {
inner: Mutex<Inner>,
}

// 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. 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 {
/// True if the ability to pause time is enabled.
Expand Down Expand Up @@ -199,6 +210,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()
Expand Down Expand Up @@ -241,6 +256,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")
Expand Down
14 changes: 14 additions & 0 deletions tokio/tests/rt_time_start_paused.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit ee1c940

Please sign in to comment.