diff --git a/CHANGELOG.md b/CHANGELOG.md index 14862c84..e987fea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ All notable changes to this project will be documented in this file. * Reduce intermediate payload copies in bounded MPSC receives, especially for large inline messages. * Transfer reclaimed broadcast head messages directly to receivers, avoiding an extra payload `Arc` clone and unnecessary blocked-sender bookkeeping. +* Reduce the cost of acquiring and releasing an uncontended `Mutex` by avoiding its internal wait-queue lock. * Reduce the cost of initializing an uncontended `OnceCell` by avoiding its internal wait-queue lock. * Reduce the cost of polling a pending oneshot receive again from the same task, which now keeps its registered waker instead of cloning and dropping it. * Reduce the state kept by futures waiting for a `Mutex`, `RwLock`, `Semaphore`, `Once`, `OnceCell`, or bounded broadcast capacity, making lock, permit acquisition, and bounded broadcast send futures smaller. diff --git a/asyncband/src/internal/semaphore.rs b/asyncband/src/internal/semaphore.rs index 2964757a..68d5785b 100644 --- a/asyncband/src/internal/semaphore.rs +++ b/asyncband/src/internal/semaphore.rs @@ -164,17 +164,6 @@ impl Semaphore { self.distribute_permits(n, self.waiters.lock()); } - /// Releases `n` permits by acquiring the queue lock directly. - /// - /// Use this when no permits can be available, as when a mutex guard holds its only permit, - /// to skip the speculative attempt in [`release`](Self::release). - /// This method also works when permits are available. - pub fn release_with_lock(&self, n: usize) { - if n != 0 { - self.distribute_permits(n, self.waiters.lock()); - } - } - /// Releases `n` permits only if the queue contains waiters when locked. #[cfg(feature = "broadcast")] pub fn release_if_waiters(&self, n: usize) { @@ -533,24 +522,6 @@ mod tests { assert!(semaphore.waiters.lock().is_empty()); } - #[test] - fn release_with_lock_hands_permits_to_waiters() { - let semaphore = Semaphore::new(1); - let counter = Arc::new(WakeCounter(AtomicUsize::new(0))); - let waker = Waker::from(counter.clone()); - assert!(semaphore.try_acquire(1)); - let mut acquire = semaphore.acquire(1); - assert!(acquire.poll_once(&waker).is_pending()); - - semaphore.release_with_lock(1); - assert_eq!(counter.0.load(Ordering::Relaxed), 1); - assert_eq!(semaphore.available_permits(), 0); - assert!(acquire.poll_once(&waker).is_ready()); - - semaphore.release_with_lock(1); - assert_eq!(semaphore.available_permits(), 1); - } - #[test] fn release_distributes_permits_across_multiple_batches() { const WAITER_COUNT: usize = WakerBatch::STACK_SIZE * 2 + 1; diff --git a/asyncband/src/mutex/mod.rs b/asyncband/src/mutex/mod.rs index da8e81c2..2b723a2f 100644 --- a/asyncband/src/mutex/mod.rs +++ b/asyncband/src/mutex/mod.rs @@ -120,7 +120,9 @@ impl Mutex { /// let mutex = Mutex::new(5); /// ``` pub const fn new(t: T) -> Self { - let s = semaphore::Semaphore::new(1); + // Each lock takes two of three permits, so only one can run at a time. The spare + // permit lets an uncontended release avoid locking the semaphore's wait queue. + let s = semaphore::Semaphore::new(3); let c = UnsafeCell::new(t); Self { s, c } } @@ -162,7 +164,7 @@ impl Mutex { /// # } /// ``` pub async fn lock(&self) -> MutexGuard<'_, T> { - self.s.acquire(1).await; + self.s.acquire(2).await; MutexGuard { lock: self } } @@ -179,7 +181,7 @@ impl Mutex { /// assert_eq!(2, *guard); /// ``` pub fn try_lock(&self) -> Option> { - if self.s.try_acquire(1) { + if self.s.try_acquire(2) { let guard = MutexGuard { lock: self }; Some(guard) } else { @@ -212,7 +214,7 @@ impl Mutex { /// # } /// ``` pub async fn lock_owned(self: Arc) -> OwnedMutexGuard { - self.s.acquire(1).await; + self.s.acquire(2).await; OwnedMutexGuard { lock: self } } @@ -233,7 +235,7 @@ impl Mutex { /// assert_eq!(2, *guard); /// ``` pub fn try_lock_owned(self: Arc) -> Option> { - if self.s.try_acquire(1) { + if self.s.try_acquire(2) { let guard = OwnedMutexGuard { lock: self }; Some(guard) } else { @@ -278,7 +280,7 @@ unsafe impl Sync for MutexGuard<'_, T> {} impl Drop for MutexGuard<'_, T> { fn drop(&mut self) { - self.lock.s.release_with_lock(1); + self.lock.s.release(2); } } @@ -445,7 +447,7 @@ unsafe impl Sync for OwnedMutexGuard {} impl Drop for OwnedMutexGuard { fn drop(&mut self) { - self.lock.s.release_with_lock(1); + self.lock.s.release(2); } } @@ -641,7 +643,7 @@ unsafe impl Sync for MappedMutexGuard<'_, T> {} impl Drop for MappedMutexGuard<'_, T> { fn drop(&mut self) { - self.s.release_with_lock(1); + self.s.release(2); } } @@ -840,7 +842,7 @@ unsafe impl Sync for OwnedMapp impl Drop for OwnedMappedMutexGuard { fn drop(&mut self) { - self.lock.s.release_with_lock(1); + self.lock.s.release(2); } }