From b87debd9ac7ce73ec5bd4bc3433e74111589bd9f Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Thu, 20 Jun 2024 06:37:27 +0200 Subject: [PATCH] feat: Optimize for timer timeout extension 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. --- .github/workflows/ci.yml | 28 +++++++++++ Cargo.toml | 5 ++ benches/timer.rs | 100 +++++++++++++++++++++++++++++++++++++++ src/sources/timer.rs | 14 ++++++ 4 files changed, 147 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); diff --git a/src/sources/timer.rs b/src/sources/timer.rs index 7a4cba85..b6331c9d 100644 --- a/src/sources/timer.rs +++ b/src/sources/timer.rs @@ -229,8 +229,19 @@ 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() + .rev() .find(|data| data.counter == counter) .map(|data| data.token.take()); } @@ -265,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() @@ -272,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)) } @@ -281,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 }