Skip to content

Commit ee1c940

Browse files
authored
time: Improve Instant::now() perf with test-util (#5513)
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.
1 parent 815d89a commit ee1c940

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

benches/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ version = "0.0.0"
44
publish = false
55
edition = "2018"
66

7+
[features]
8+
test-util = ["tokio/test-util"]
9+
710
[dependencies]
811
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
912
bencher = "0.1.5"
@@ -67,3 +70,8 @@ harness = false
6770
name = "copy"
6871
path = "copy.rs"
6972
harness = false
73+
74+
[[bench]]
75+
name = "time_now"
76+
path = "time_now.rs"
77+
harness = false

benches/time_now.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Benchmark spawning a task onto the basic and threaded Tokio executors.
2+
//! This essentially measure the time to enqueue a task in the local and remote
3+
//! case.
4+
5+
#[macro_use]
6+
extern crate bencher;
7+
8+
use bencher::{black_box, Bencher};
9+
10+
fn time_now_current_thread(bench: &mut Bencher) {
11+
let rt = tokio::runtime::Builder::new_current_thread()
12+
.enable_time()
13+
.build()
14+
.unwrap();
15+
16+
bench.iter(|| {
17+
rt.block_on(async {
18+
black_box(tokio::time::Instant::now());
19+
})
20+
})
21+
}
22+
23+
bencher::benchmark_group!(time_now, time_now_current_thread,);
24+
25+
bencher::benchmark_main!(time_now);

tokio/src/time/clock.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ cfg_not_test_util! {
3030
cfg_test_util! {
3131
use crate::time::{Duration, Instant};
3232
use crate::loom::sync::Mutex;
33+
use crate::loom::sync::atomic::Ordering;
34+
use std::sync::atomic::AtomicBool as StdAtomicBool;
3335

3436
cfg_rt! {
3537
#[track_caller]
@@ -65,6 +67,15 @@ cfg_test_util! {
6567
inner: Mutex<Inner>,
6668
}
6769

70+
// Used to track if the clock was ever paused. This is an optimization to
71+
// avoid touching the mutex if `test-util` was accidentally enabled in
72+
// release mode.
73+
//
74+
// A static is used so we can avoid accessing the thread-local as well. The
75+
// `std` AtomicBool is used directly because loom does not support static
76+
// atomics.
77+
static DID_PAUSE_CLOCK: StdAtomicBool = StdAtomicBool::new(false);
78+
6879
#[derive(Debug)]
6980
struct Inner {
7081
/// True if the ability to pause time is enabled.
@@ -199,6 +210,10 @@ cfg_test_util! {
199210

200211
/// Returns the current instant, factoring in frozen time.
201212
pub(crate) fn now() -> Instant {
213+
if !DID_PAUSE_CLOCK.load(Ordering::Acquire) {
214+
return Instant::from_std(std::time::Instant::now());
215+
}
216+
202217
with_clock(|maybe_clock| {
203218
Ok(if let Some(clock) = maybe_clock {
204219
clock.now()
@@ -241,6 +256,9 @@ cfg_test_util! {
241256
This is the default Runtime used by `#[tokio::test].");
242257
}
243258

259+
// Track that we paused the clock
260+
DID_PAUSE_CLOCK.store(true, Ordering::Release);
261+
244262
let elapsed = match inner.unfrozen.as_ref() {
245263
Some(v) => v.elapsed(),
246264
None => return Err("time is already frozen")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![cfg(all(feature = "full"))]
2+
3+
use tokio::time::{Duration, Instant};
4+
5+
#[tokio::test(start_paused = true)]
6+
async fn test_start_paused() {
7+
let now = Instant::now();
8+
9+
// Pause a few times w/ std sleep and ensure `now` stays the same
10+
for _ in 0..5 {
11+
std::thread::sleep(Duration::from_millis(1));
12+
assert_eq!(now, Instant::now());
13+
}
14+
}

0 commit comments

Comments
 (0)