Skip to content

Commit 051c6ec

Browse files
committed
Port changes in BinaryHeap::append
This commit ports the change in the rebuild heuristic used by `BinaryHeap::append()` that was added in rust-lang/rust#77435: "Change BinaryHeap::append rebuild heuristic". See also the discussion in rust-lang/rust#77433: "Suboptimal performance of BinaryHeap::append" for more information on how the new heuristic was chosen. It also ports the new private method `.rebuild_tail()` now used by `std::collections::BinaryHeap::append()` from rust-lang/rust#78681: "Improve rebuilding behaviour of BinaryHeap::retain". Note that Rust 1.60.0 adds the clippy lint `manual_bits` which warns against code used here. We suppress the lint instead of following the upstream patch which now uses `usize::BITS`, since this was stabilized in Rust 1.53.0 and this crate's MSRV is currently 1.36.0.
1 parent 478bb1f commit 051c6ec

File tree

1 file changed

+42
-23
lines changed

1 file changed

+42
-23
lines changed

src/binary_heap.rs

+42-23
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,45 @@ impl<T, C: Compare<T>> BinaryHeap<T, C> {
11961196
self.drain();
11971197
}
11981198

1199+
/// Rebuild assuming data[0..start] is still a proper heap.
1200+
fn rebuild_tail(&mut self, start: usize) {
1201+
if start == self.len() {
1202+
return;
1203+
}
1204+
1205+
let tail_len = self.len() - start;
1206+
1207+
// `usize::BITS` requires Rust 1.53.0 or greater.
1208+
#[allow(clippy::manual_bits)]
1209+
#[inline(always)]
1210+
fn log2_fast(x: usize) -> usize {
1211+
8 * size_of::<usize>() - (x.leading_zeros() as usize) - 1
1212+
}
1213+
1214+
// `rebuild` takes O(self.len()) operations
1215+
// and about 2 * self.len() comparisons in the worst case
1216+
// while repeating `sift_up` takes O(tail_len * log(start)) operations
1217+
// and about 1 * tail_len * log_2(start) comparisons in the worst case,
1218+
// assuming start >= tail_len. For larger heaps, the crossover point
1219+
// no longer follows this reasoning and was determined empirically.
1220+
let better_to_rebuild = if start < tail_len {
1221+
true
1222+
} else if self.len() <= 2048 {
1223+
2 * self.len() < tail_len * log2_fast(start)
1224+
} else {
1225+
2 * self.len() < tail_len * 11
1226+
};
1227+
1228+
if better_to_rebuild {
1229+
self.rebuild();
1230+
} else {
1231+
for i in start..self.len() {
1232+
// SAFETY: The index `i` is always less than self.len().
1233+
unsafe { self.sift_up(0, i) };
1234+
}
1235+
}
1236+
}
1237+
11991238
fn rebuild(&mut self) {
12001239
let mut n = self.len() / 2;
12011240
while n > 0 {
@@ -1233,31 +1272,11 @@ impl<T, C: Compare<T>> BinaryHeap<T, C> {
12331272
swap(self, other);
12341273
}
12351274

1236-
if other.is_empty() {
1237-
return;
1238-
}
1239-
1240-
#[inline(always)]
1241-
fn log2_fast(x: usize) -> usize {
1242-
8 * size_of::<usize>() - (x.leading_zeros() as usize) - 1
1243-
}
1275+
let start = self.data.len();
12441276

1245-
// `rebuild` takes O(len1 + len2) operations
1246-
// and about 2 * (len1 + len2) comparisons in the worst case
1247-
// while `extend` takes O(len2 * log_2(len1)) operations
1248-
// and about 1 * len2 * log_2(len1) comparisons in the worst case,
1249-
// assuming len1 >= len2.
1250-
#[inline]
1251-
fn better_to_rebuild(len1: usize, len2: usize) -> bool {
1252-
2 * (len1 + len2) < len2 * log2_fast(len1)
1253-
}
1277+
self.data.append(&mut other.data);
12541278

1255-
if better_to_rebuild(self.len(), other.len()) {
1256-
self.data.append(&mut other.data);
1257-
self.rebuild();
1258-
} else {
1259-
self.extend(other.drain());
1260-
}
1279+
self.rebuild_tail(start);
12611280
}
12621281
}
12631282

0 commit comments

Comments
 (0)