Skip to content

Commit 436959e

Browse files
committed
Add PeekMut::refresh
This improves the useability of heaps for priority-based work queues. In certain scenarios, modifications on the most relevant or critical items are performed until a condition that determines the work items have been sufficiently addressed. The loop will repeatedly access the most critical item and put it back in a sorted position when it is complete. Crucially, due to the ordering invariant we know that all work was performed when the completed item remains the most critical. Getting this information from the heap position avoids a (potentially more costly) check on the item state itself. A customized `drop` with boolean result would avoid up to two more comparisons performed in both the last no-op refresh and Drop code but this occurs once in each execution of the above scenario whereas refresh occurs any number of times. Also note that the comparison overhead of Drop is only taken if the element is mutably inspected to determine the end condition, i.e. not when refresh itself is the break condition.
1 parent 91a0e16 commit 436959e

File tree

1 file changed

+78
-4
lines changed
  • library/alloc/src/collections/binary_heap

1 file changed

+78
-4
lines changed

library/alloc/src/collections/binary_heap/mod.rs

+78-4
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,74 @@ impl<T: Ord, A: Allocator> DerefMut for PeekMut<'_, T, A> {
359359
}
360360

361361
impl<'a, T: Ord, A: Allocator> PeekMut<'a, T, A> {
362+
/// Sifts the current element to its new position.
363+
///
364+
/// Afterwards refers to the new element. Returns if the element changed.
365+
///
366+
/// ## Examples
367+
///
368+
/// The condition can be used to upper bound all elements in the heap. When only few elements
369+
/// are affected, the heap's sort ensures this is faster than a reconstruction from the raw
370+
/// element list and requires no additional allocation.
371+
///
372+
/// ```
373+
/// #![feature(binary_heap_peek_mut_refresh)]
374+
/// use std::collections::BinaryHeap;
375+
///
376+
/// let mut heap: BinaryHeap<u32> = (0..128).collect();
377+
/// let mut peek = heap.peek_mut().unwrap();
378+
///
379+
/// loop {
380+
/// *peek = 99;
381+
///
382+
/// if !peek.refresh() {
383+
/// break;
384+
/// }
385+
/// }
386+
///
387+
/// // Post condition, this is now an upper bound.
388+
/// assert!(*peek < 100);
389+
/// ```
390+
///
391+
/// When the element remains the maximum after modification, the peek remains unchanged:
392+
///
393+
/// ```
394+
/// #![feature(binary_heap_peek_mut_refresh)]
395+
/// use std::collections::BinaryHeap;
396+
///
397+
/// let mut heap: BinaryHeap<u32> = [1, 2, 3].into();
398+
/// let mut peek = heap.peek_mut().unwrap();
399+
///
400+
/// assert_eq!(*peek, 3);
401+
/// *peek = 42;
402+
///
403+
/// // When we refresh, the peek is updated to the new maximum.
404+
/// assert!(!peek.refresh(), "42 is even larger than 3");
405+
/// assert_eq!(*peek, 42);
406+
/// ```
407+
#[unstable(feature = "binary_heap_peek_mut_refresh", issue = "138355")]
408+
#[must_use = "is equivalent to dropping and getting a new PeekMut except for return information"]
409+
pub fn refresh(&mut self) -> bool {
410+
// The length of the underlying heap is unchanged by sifting down. The value stored for leak
411+
// amplification thus remains accurate. We erase the leak amplification firstly because the
412+
// operation is then equivalent to constructing a new PeekMut and secondly this avoids any
413+
// future complication where original_len being non-empty would be interpreted as the heap
414+
// having been leak amplified instead of checking the heap itself.
415+
if let Some(original_len) = self.original_len.take() {
416+
// SAFETY: This is how many elements were in the Vec at the time of
417+
// the BinaryHeap::peek_mut call.
418+
unsafe { self.heap.data.set_len(original_len.get()) };
419+
420+
// The length of the heap did not change by sifting, upholding our own invariants.
421+
422+
// SAFETY: PeekMut is only instantiated for non-empty heaps.
423+
(unsafe { self.heap.sift_down(0) }) != 0
424+
} else {
425+
// The element was not modified.
426+
false
427+
}
428+
}
429+
362430
/// Removes the peeked value from the heap and returns it.
363431
#[stable(feature = "binary_heap_peek_mut_pop", since = "1.18.0")]
364432
pub fn pop(mut this: PeekMut<'a, T, A>) -> T {
@@ -670,6 +738,8 @@ impl<T: Ord, A: Allocator> BinaryHeap<T, A> {
670738
/// # Safety
671739
///
672740
/// The caller must guarantee that `pos < self.len()`.
741+
///
742+
/// Returns the new position of the element.
673743
unsafe fn sift_up(&mut self, start: usize, pos: usize) -> usize {
674744
// Take out the value at `pos` and create a hole.
675745
// SAFETY: The caller guarantees that pos < self.len()
@@ -696,10 +766,12 @@ impl<T: Ord, A: Allocator> BinaryHeap<T, A> {
696766
/// Take an element at `pos` and move it down the heap,
697767
/// while its children are larger.
698768
///
769+
/// Returns the new position of the element.
770+
///
699771
/// # Safety
700772
///
701773
/// The caller must guarantee that `pos < end <= self.len()`.
702-
unsafe fn sift_down_range(&mut self, pos: usize, end: usize) {
774+
unsafe fn sift_down_range(&mut self, pos: usize, end: usize) -> usize {
703775
// SAFETY: The caller guarantees that pos < end <= self.len().
704776
let mut hole = unsafe { Hole::new(&mut self.data, pos) };
705777
let mut child = 2 * hole.pos() + 1;
@@ -719,7 +791,7 @@ impl<T: Ord, A: Allocator> BinaryHeap<T, A> {
719791
// SAFETY: child is now either the old child or the old child+1
720792
// We already proven that both are < self.len() and != hole.pos()
721793
if hole.element() >= unsafe { hole.get(child) } {
722-
return;
794+
return hole.pos();
723795
}
724796

725797
// SAFETY: same as above.
@@ -734,16 +806,18 @@ impl<T: Ord, A: Allocator> BinaryHeap<T, A> {
734806
// child == 2 * hole.pos() + 1 != hole.pos().
735807
unsafe { hole.move_to(child) };
736808
}
809+
810+
hole.pos()
737811
}
738812

739813
/// # Safety
740814
///
741815
/// The caller must guarantee that `pos < self.len()`.
742-
unsafe fn sift_down(&mut self, pos: usize) {
816+
unsafe fn sift_down(&mut self, pos: usize) -> usize {
743817
let len = self.len();
744818
// SAFETY: pos < len is guaranteed by the caller and
745819
// obviously len = self.len() <= self.len().
746-
unsafe { self.sift_down_range(pos, len) };
820+
unsafe { self.sift_down_range(pos, len) }
747821
}
748822

749823
/// Take an element at `pos` and move it all the way down the heap,

0 commit comments

Comments
 (0)