Optimize linked list popping so delete is not run everytime

This commit is contained in:
Emil Fresk 2023-01-24 11:55:48 +01:00 committed by Henrik Tjäder
parent 71b5f9438e
commit 143cd136ee
2 changed files with 28 additions and 107 deletions

View file

@ -6,9 +6,8 @@
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
pub mod monotonic;
use core::future::{poll_fn, Future}; use core::future::{poll_fn, Future};
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use core::task::{Poll, Waker}; use core::task::{Poll, Waker};
use futures_util::{ use futures_util::{
@ -18,20 +17,25 @@ use futures_util::{
pub use monotonic::Monotonic; pub use monotonic::Monotonic;
mod linked_list; mod linked_list;
mod monotonic;
use linked_list::{Link, LinkedList}; use linked_list::{Link, LinkedList};
/// Holds a waker and at which time instant this waker shall be awoken. /// Holds a waker and at which time instant this waker shall be awoken.
struct WaitingWaker<Mono: Monotonic> { struct WaitingWaker<Mono: Monotonic> {
waker: Waker, // This is alway initialized when used, we create this struct on the async stack and then
// initialize the waker field in the `poll_fn` closure (we then know the waker)
waker: MaybeUninit<Waker>,
release_at: Mono::Instant, release_at: Mono::Instant,
was_poped: AtomicBool,
} }
impl<Mono: Monotonic> Clone for WaitingWaker<Mono> { impl<Mono: Monotonic> Clone for WaitingWaker<Mono> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
waker: self.waker.clone(), waker: MaybeUninit::new(unsafe { self.waker.assume_init_ref() }.clone()),
release_at: self.release_at, release_at: self.release_at,
was_poped: AtomicBool::new(self.was_poped.load(Ordering::Relaxed)),
} }
} }
} }
@ -109,12 +113,15 @@ impl<Mono: Monotonic> TimerQueue<Mono> {
let head = self.queue.pop_if(|head| { let head = self.queue.pop_if(|head| {
release_at = Some(head.release_at); release_at = Some(head.release_at);
Mono::now() >= head.release_at let should_pop = Mono::now() >= head.release_at;
head.was_poped.store(should_pop, Ordering::Relaxed);
should_pop
}); });
match (head, release_at) { match (head, release_at) {
(Some(link), _) => { (Some(link), _) => {
link.waker.wake(); link.waker.assume_init().wake();
} }
(None, Some(instant)) => { (None, Some(instant)) => {
Mono::enable_timer(); Mono::enable_timer();
@ -181,26 +188,28 @@ impl<Mono: Monotonic> TimerQueue<Mono> {
); );
} }
let mut first_run = true;
let queue = &self.queue;
let mut link = Link::new(WaitingWaker { let mut link = Link::new(WaitingWaker {
waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await, waker: MaybeUninit::uninit(),
release_at: instant, release_at: instant,
was_poped: AtomicBool::new(false),
}); });
let mut first_run = true;
let queue = &self.queue;
let marker = &AtomicUsize::new(0); let marker = &AtomicUsize::new(0);
let dropper = OnDrop::new(|| { let dropper = OnDrop::new(|| {
queue.delete(marker.load(Ordering::Relaxed)); queue.delete(marker.load(Ordering::Relaxed));
}); });
poll_fn(|_| { poll_fn(|cx| {
if Mono::now() >= instant { if Mono::now() >= instant {
return Poll::Ready(()); return Poll::Ready(());
} }
if first_run { if first_run {
first_run = false; first_run = false;
link.val.waker.write(cx.waker().clone());
let (was_empty, addr) = queue.insert(&mut link); let (was_empty, addr) = queue.insert(&mut link);
marker.store(addr, Ordering::Relaxed); marker.store(addr, Ordering::Relaxed);
@ -214,8 +223,13 @@ impl<Mono: Monotonic> TimerQueue<Mono> {
}) })
.await; .await;
// Make sure that our link is deleted from the list before we drop this stack if link.val.was_poped.load(Ordering::Relaxed) {
drop(dropper); // If it was poped from the queue there is no need to run delete
dropper.defuse();
} else {
// Make sure that our link is deleted from the list before we drop this stack
drop(dropper);
}
} }
} }
@ -241,96 +255,3 @@ impl<F: FnOnce()> Drop for OnDrop<F> {
unsafe { self.f.as_ptr().read()() } unsafe { self.f.as_ptr().read()() }
} }
} }
// -------- Test program ---------
//
//
// use systick_monotonic::{Systick, TimerQueue};
//
// // same panicking *behavior* as `panic-probe` but doesn't print a panic message
// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked
// #[defmt::panic_handler]
// fn panic() -> ! {
// cortex_m::asm::udf()
// }
//
// /// Terminates the application and makes `probe-run` exit with exit-code = 0
// pub fn exit() -> ! {
// loop {
// cortex_m::asm::bkpt();
// }
// }
//
// defmt::timestamp!("{=u64:us}", {
// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert();
//
// time_us.ticks() as u64
// });
//
// make_systick_timer_queue!(MONO, Systick<1_000>);
//
// #[rtic::app(
// device = nrf52832_hal::pac,
// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5],
// )]
// mod app {
// use super::{Systick, MONO};
// use fugit::ExtU32;
//
// #[shared]
// struct Shared {}
//
// #[local]
// struct Local {}
//
// #[init]
// fn init(cx: init::Context) -> (Shared, Local) {
// defmt::println!("init");
//
// let systick = Systick::start(cx.core.SYST, 64_000_000);
//
// defmt::println!("initializing monotonic");
//
// MONO.initialize(systick);
//
// async_task::spawn().ok();
// async_task2::spawn().ok();
// async_task3::spawn().ok();
//
// (Shared {}, Local {})
// }
//
// #[idle]
// fn idle(_: idle::Context) -> ! {
// defmt::println!("idle");
//
// loop {
// core::hint::spin_loop();
// }
// }
//
// #[task]
// async fn async_task(_: async_task::Context) {
// loop {
// defmt::println!("async task waiting for 1 second");
// MONO.delay(1.secs()).await;
// }
// }
//
// #[task]
// async fn async_task2(_: async_task2::Context) {
// loop {
// defmt::println!(" async task 2 waiting for 0.5 second");
// MONO.delay(500.millis()).await;
// }
// }
//
// #[task]
// async fn async_task3(_: async_task3::Context) {
// loop {
// defmt::println!(" async task 3 waiting for 0.2 second");
// MONO.delay(200.millis()).await;
// }
// }
// }
//

View file

@ -5,7 +5,7 @@ use core::sync::atomic::{AtomicPtr, Ordering};
use critical_section as cs; use critical_section as cs;
/// A sorted linked list for the timer queue. /// A sorted linked list for the timer queue.
pub struct LinkedList<T> { pub(crate) struct LinkedList<T> {
head: AtomicPtr<Link<T>>, head: AtomicPtr<Link<T>>,
} }
@ -156,7 +156,7 @@ impl<T: PartialOrd + Clone> LinkedList<T> {
/// A link in the linked list. /// A link in the linked list.
pub struct Link<T> { pub struct Link<T> {
val: T, pub(crate) val: T,
next: AtomicPtr<Link<T>>, next: AtomicPtr<Link<T>>,
_up: PhantomPinned, _up: PhantomPinned,
} }