From 842ce4f8dc2b5e58cdd6198168b8ae963553e62e Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Fri, 17 May 2024 14:26:00 +0200 Subject: [PATCH 1/4] add benches for timer extension add benchmarks specifically targeting the use-case of extending a timer by removing and re-registering --- .github/workflows/ci.yml | 28 +++++++++++ Cargo.toml | 5 ++ benches/timer.rs | 100 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 benches/timer.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcc180d4..67fcf5b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,6 +131,20 @@ jobs: command: update args: --package log --precise 0.4.16 + - name: Downgrade regex + uses: actions-rs/cargo@v1 + if: ${{ matrix.rust == '1.63.0' }} + with: + command: update + args: --package regex --precise 1.9.6 + + - name: Downgrade half + uses: actions-rs/cargo@v1 + if: ${{ matrix.rust == '1.63.0' || matrix.rust == '1.69.0' }} + with: + command: update + args: --package half --precise 2.2.1 + - name: Run tests uses: actions-rs/cargo@v1 with: @@ -174,6 +188,20 @@ jobs: toolchain: ${{ matrix.rust }} override: true + - name: Downgrade regex + uses: actions-rs/cargo@v1 + if: ${{ matrix.rust == '1.63.0' }} + with: + command: update + args: --package regex --precise 1.9.6 + + - name: Downgrade half + uses: actions-rs/cargo@v1 + if: ${{ matrix.rust == '1.63.0' }} + with: + command: update + args: --package half --precise 2.2.1 + - name: Run tests uses: actions-rs/cargo@v1 with: diff --git a/Cargo.toml b/Cargo.toml index adec26c5..648bbaab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ nix = { version = "0.29", default-features = false, features = ["signal"], optio [dev-dependencies] futures = "0.3.5" rustix = { version = "0.38", default-features = false, features = ["net"] } +criterion = { version = "0.4" } [features] block_on = ["pin-utils"] @@ -48,3 +49,7 @@ rustdoc-args = ["--cfg", "docsrs"] [[test]] name = "signals" harness = false + +[[bench]] +name = "timer" +harness = false \ No newline at end of file diff --git a/benches/timer.rs b/benches/timer.rs new file mode 100644 index 00000000..144d77de --- /dev/null +++ b/benches/timer.rs @@ -0,0 +1,100 @@ +use std::time::Duration; + +use calloop::timer::TimeoutAction; +use criterion::{criterion_group, criterion_main, Criterion}; + +fn single(c: &mut Criterion) { + let mut event_loop = calloop::EventLoop::<()>::try_new().unwrap(); + let loop_handle = event_loop.handle(); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10)); + let mut timeout_token = loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + c.bench_function("extend_single", |b| { + b.iter(|| { + loop_handle.remove(timeout_token); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10)); + timeout_token = loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + event_loop.dispatch(Some(Duration::ZERO), &mut ()).unwrap(); + }); + }); +} + +fn mixed(c: &mut Criterion) { + let mut event_loop = calloop::EventLoop::<()>::try_new().unwrap(); + let loop_handle = event_loop.handle(); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10 - 1)); + loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10)); + let mut timeout_token = loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(90 * 10)); + loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + c.bench_function("extend_mixed", |b| { + b.iter(|| { + loop_handle.remove(timeout_token); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10)); + timeout_token = loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + event_loop.dispatch(Some(Duration::ZERO), &mut ()).unwrap(); + }); + }); +} + +fn mixed_multiple(c: &mut Criterion) { + let mut event_loop = calloop::EventLoop::<()>::try_new().unwrap(); + let loop_handle = event_loop.handle(); + + for _ in 0..1000 { + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10 - 1)); + loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + } + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10)); + let mut timeout_token = loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + for _ in 0..1000 { + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(90 * 10)); + loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + } + + c.bench_function("extend_mixed_many", |b| { + b.iter(|| { + loop_handle.remove(timeout_token); + + let timer = calloop::timer::Timer::from_duration(Duration::from_secs(60 * 10)); + timeout_token = loop_handle + .insert_source(timer, |_, _, _| TimeoutAction::Drop) + .unwrap(); + + event_loop.dispatch(Some(Duration::ZERO), &mut ()).unwrap(); + }); + }); +} + +criterion_group!(benches, single, mixed, mixed_multiple); +criterion_main!(benches); From 22418819ba75714a6ad82496fa64d98af4a29044 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Fri, 17 May 2024 21:07:12 +0200 Subject: [PATCH 2/4] timers: short-cut timeout extension pattern in case we constantly extend the same timer the head might already point to the timer we want to cancel. in this case we can directly remove the timer to reduce building up the heap unnecessarily. --- src/sources/timer.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sources/timer.rs b/src/sources/timer.rs index 7a4cba85..2b164dc3 100644 --- a/src/sources/timer.rs +++ b/src/sources/timer.rs @@ -229,6 +229,16 @@ impl TimerWheel { } pub(crate) fn cancel(&mut self, counter: u32) { + if self + .heap + .peek() + .map(|data| data.counter == counter) + .unwrap_or(false) + { + self.heap.pop(); + return; + }; + self.heap .iter() .find(|data| data.counter == counter) From a5a1945a626556b11436aae41323e37aafc3cce8 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Fri, 17 May 2024 21:09:09 +0200 Subject: [PATCH 3/4] timers: optimize for timeout extension pattern it is more likely an older timeout gets canceled and replaced. reversing the search might yield some performance improvement in this case --- src/sources/timer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sources/timer.rs b/src/sources/timer.rs index 2b164dc3..09ebba09 100644 --- a/src/sources/timer.rs +++ b/src/sources/timer.rs @@ -241,6 +241,7 @@ impl TimerWheel { self.heap .iter() + .rev() .find(|data| data.counter == counter) .map(|data| data.token.take()); } From 24a45449d525ec8baa112d8dfc5a24ad52019599 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Fri, 17 May 2024 21:09:28 +0200 Subject: [PATCH 4/4] timers: inline hint for timeouts --- src/sources/timer.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sources/timer.rs b/src/sources/timer.rs index 09ebba09..b6331c9d 100644 --- a/src/sources/timer.rs +++ b/src/sources/timer.rs @@ -276,6 +276,7 @@ impl TimerWheel { // trait implementations for TimeoutData impl std::cmp::Ord for TimeoutData { + #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { // earlier values have priority self.deadline.cmp(&other.deadline).reverse() @@ -283,6 +284,7 @@ impl std::cmp::Ord for TimeoutData { } impl std::cmp::PartialOrd for TimeoutData { + #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } @@ -292,6 +294,7 @@ impl std::cmp::PartialOrd for TimeoutData { // and the type is private, so ignore its coverage impl std::cmp::PartialEq for TimeoutData { #[cfg_attr(feature = "nightly_coverage", coverage(off))] + #[inline] fn eq(&self, other: &Self) -> bool { self.deadline == other.deadline }