Skip to content

Commit 24efac1

Browse files
committed
Optimized BTreeMap::merge using CursorMut
1 parent 1b50859 commit 24efac1

3 files changed

Lines changed: 219 additions & 72 deletions

File tree

library/alloc/src/collections/btree/append.rs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,6 @@ impl<K, V> Root<K, V> {
3333
self.bulk_push(iter, length, alloc)
3434
}
3535

36-
/// Merges all key-value pairs from the union of two ascending iterators,
37-
/// incrementing a `length` variable along the way. The latter makes it
38-
/// easier for the caller to avoid a leak when a drop handler panicks.
39-
///
40-
/// If both iterators produce the same key, this method constructs a pair using the
41-
/// key from the left iterator and calls on a closure `f` to return a value given
42-
/// the conflicting key and value from left and right iterators.
43-
///
44-
/// If you want the tree to end up in a strictly ascending order, like for
45-
/// a `BTreeMap`, both iterators should produce keys in strictly ascending
46-
/// order, each greater than all keys in the tree, including any keys
47-
/// already in the tree upon entry.
48-
pub(super) fn merge_from_sorted_iters_with<I, A: Allocator + Clone>(
49-
&mut self,
50-
left: I,
51-
right: I,
52-
length: &mut usize,
53-
alloc: A,
54-
f: impl FnMut(&K, V, V) -> V,
55-
) where
56-
K: Ord,
57-
I: Iterator<Item = (K, V)> + FusedIterator,
58-
{
59-
// We prepare to merge `left` and `right` into a sorted sequence in linear time.
60-
let iter = MergeIterWith { inner: MergeIterInner::new(left, right), f };
61-
62-
// Meanwhile, we build a tree from the sorted sequence in linear time.
63-
self.bulk_push(iter, length, alloc)
64-
}
65-
6636
/// Pushes all key-value pairs to the end of the tree, incrementing a
6737
/// `length` variable along the way. The latter makes it easier for the
6838
/// caller to avoid a leak when the iterator panicks.
@@ -145,33 +115,3 @@ where
145115
}
146116
}
147117
}
148-
149-
/// An iterator for merging two sorted sequences into one with
150-
/// a callback function to return a value on conflicting keys
151-
struct MergeIterWith<F, K, V, I: Iterator<Item = (K, V)>> {
152-
inner: MergeIterInner<I>,
153-
f: F,
154-
}
155-
156-
impl<F, K: Ord, V, I> Iterator for MergeIterWith<F, K, V, I>
157-
where
158-
F: FnMut(&K, V, V) -> V,
159-
I: Iterator<Item = (K, V)> + FusedIterator,
160-
{
161-
type Item = (K, V);
162-
163-
/// If two keys are equal, returns the key from the left and uses `f` to return
164-
/// a value given the conflicting key and values from left and right
165-
fn next(&mut self) -> Option<(K, V)> {
166-
let (a_next, b_next) = self.inner.nexts(|a: &(K, V), b: &(K, V)| K::cmp(&a.0, &b.0));
167-
match (a_next, b_next) {
168-
(Some((a_k, a_v)), Some((_, b_v))) => Some({
169-
let next_val = (self.f)(&a_k, a_v, b_v);
170-
(a_k, next_val)
171-
}),
172-
(Some(a), None) => Some(a),
173-
(None, Some(b)) => Some(b),
174-
(None, None) => None,
175-
}
176-
}
177-
}

library/alloc/src/collections/btree/map.rs

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ impl<K, V, A: Allocator + Clone> BTreeMap<K, V, A> {
12871287
/// assert_eq!(a[&5], "f");
12881288
/// ```
12891289
#[unstable(feature = "btree_merge", issue = "152152")]
1290-
pub fn merge(&mut self, mut other: Self, conflict: impl FnMut(&K, V, V) -> V)
1290+
pub fn merge(&mut self, mut other: Self, mut conflict: impl FnMut(&K, V, V) -> V)
12911291
where
12921292
K: Ord,
12931293
A: Clone,
@@ -1303,16 +1303,75 @@ impl<K, V, A: Allocator + Clone> BTreeMap<K, V, A> {
13031303
return;
13041304
}
13051305

1306-
let self_iter = mem::replace(self, Self::new_in((*self.alloc).clone())).into_iter();
1307-
let other_iter = mem::replace(&mut other, Self::new_in((*self.alloc).clone())).into_iter();
1308-
let root = self.root.get_or_insert_with(|| Root::new((*self.alloc).clone()));
1309-
root.merge_from_sorted_iters_with(
1310-
self_iter,
1311-
other_iter,
1312-
&mut self.length,
1313-
(*self.alloc).clone(),
1314-
conflict,
1315-
)
1306+
let mut other_iter = other.into_iter();
1307+
let (first_other_key, first_other_val) = other_iter.next().unwrap();
1308+
1309+
// find the first gap that has the smallest key greater than or equal to
1310+
// the first key from other
1311+
let mut self_cursor = self.lower_bound_mut(Bound::Included(&first_other_key));
1312+
1313+
if let Some((self_key, _)) = self_cursor.peek_next() {
1314+
match K::cmp(&first_other_key, self_key) {
1315+
Ordering::Equal => {
1316+
self_cursor.with_next(|self_key, self_val| {
1317+
conflict(self_key, self_val, first_other_val)
1318+
});
1319+
}
1320+
Ordering::Less =>
1321+
// SAFETY: we know our other_key's ordering is less than self_key,
1322+
// so inserting before will guarantee sorted order
1323+
unsafe {
1324+
self_cursor.insert_before_unchecked(first_other_key, first_other_val);
1325+
},
1326+
Ordering::Greater => {
1327+
unreachable!("Cursor's peek_next should return None.");
1328+
}
1329+
}
1330+
} else {
1331+
// SAFETY: reaching here means our cursor is at the end
1332+
// self BTreeMap so we just insert other_key here
1333+
unsafe {
1334+
self_cursor.insert_before_unchecked(first_other_key, first_other_val);
1335+
}
1336+
}
1337+
1338+
for (other_key, other_val) in other_iter {
1339+
loop {
1340+
if let Some((self_key, _)) = self_cursor.peek_next() {
1341+
match K::cmp(&other_key, self_key) {
1342+
Ordering::Equal => {
1343+
self_cursor.with_next(|self_key, self_val| {
1344+
conflict(self_key, self_val, other_val)
1345+
});
1346+
break;
1347+
}
1348+
Ordering::Less => {
1349+
// SAFETY: we know our other_key's ordering is less than self_key,
1350+
// so inserting before will guarantee sorted order
1351+
unsafe {
1352+
self_cursor.insert_before_unchecked(other_key, other_val);
1353+
}
1354+
break;
1355+
}
1356+
Ordering::Greater => {
1357+
// FIXME: instead of doing a linear search here,
1358+
// this can be optimized to search the tree by starting
1359+
// from self_cursor and going towards the root and then
1360+
// back down to the proper node -- that should probably
1361+
// be a new method on Cursor*.
1362+
self_cursor.next();
1363+
}
1364+
}
1365+
} else {
1366+
// SAFETY: reaching here means our cursor is at the end
1367+
// self BTreeMap so we just insert other_key here
1368+
unsafe {
1369+
self_cursor.insert_before_unchecked(other_key, other_val);
1370+
}
1371+
break;
1372+
}
1373+
}
1374+
}
13161375
}
13171376

13181377
/// Constructs a double-ended iterator over a sub-range of elements in the map.
@@ -3337,6 +3396,37 @@ impl<'a, K, V, A> CursorMutKey<'a, K, V, A> {
33373396

33383397
// Now the tree editing operations
33393398
impl<'a, K: Ord, V, A: Allocator + Clone> CursorMutKey<'a, K, V, A> {
3399+
/// Calls a function with ownership of the next element's key and
3400+
/// and value and expects it to return a value to write
3401+
/// back to the next element's key and value. The cursor is not
3402+
/// advanced forward.
3403+
///
3404+
/// If the cursor is at the end of the map then the function is not called
3405+
/// and this essentially does not do anything.
3406+
///
3407+
/// # Safety
3408+
///
3409+
/// You must ensure that the `BTreeMap` invariants are maintained.
3410+
/// Specifically:
3411+
///
3412+
/// * The next element's key must be unique in the tree.
3413+
/// * All keys in the tree must remain in sorted order.
3414+
#[allow(dead_code)] /* This function exists for consistency with CursorMut */
3415+
pub(super) fn with_next(&mut self, f: impl FnOnce(K, V) -> (K, V)) {
3416+
// if `f` unwinds, the next entry is already removed leaving
3417+
// the tree in valid state.
3418+
// FIXME: Once `MaybeDangling` is implemented, we can optimize
3419+
// this through using a drop handler and transmutating CursorMutKey<K, V>
3420+
// to CursorMutKey<ManuallyDrop<K>, ManuallyDrop<V>> (see PR #152418)
3421+
if let Some((k, v)) = self.remove_next() {
3422+
// SAFETY: we remove the K, V out of the next entry,
3423+
// apply 'f' to get a new (K, V), and insert it back
3424+
// into the next entry that the cursor is pointing at
3425+
let (k, v) = f(k, v);
3426+
unsafe { self.insert_after_unchecked(k, v) };
3427+
}
3428+
}
3429+
33403430
/// Inserts a new key-value pair into the map in the gap that the
33413431
/// cursor is currently pointing to.
33423432
///
@@ -3542,6 +3632,29 @@ impl<'a, K: Ord, V, A: Allocator + Clone> CursorMutKey<'a, K, V, A> {
35423632
}
35433633

35443634
impl<'a, K: Ord, V, A: Allocator + Clone> CursorMut<'a, K, V, A> {
3635+
/// Calls a function with a reference to the next element's key and
3636+
/// ownership of its value. The function is expected to return a value
3637+
/// to write back to the next element's value. The cursor is not
3638+
/// advanced forward.
3639+
///
3640+
/// If the cursor is at the end of the map then the function is not called
3641+
/// and this essentially does not do anything.
3642+
pub(super) fn with_next(&mut self, f: impl FnOnce(&K, V) -> V) {
3643+
// FIXME: This can be optimized to not do all the removing/reinserting
3644+
// logic by using ptr::read, calling `f`, and then using ptr::write.
3645+
// if `f` unwinds, then we need to remove the entry while being careful to
3646+
// not cause UB by moving or dropping the already-dropped `V`
3647+
// for the entry. Some implementation ideas:
3648+
// https://github.com/rust-lang/rust/pull/152418#discussion_r2800232576
3649+
if let Some((k, v)) = self.remove_next() {
3650+
// SAFETY: we remove the K, V out of the next entry,
3651+
// apply 'f' to get a new V, and insert (K, V) back
3652+
// into the next entry that the cursor is pointing at
3653+
let v = f(&k, v);
3654+
unsafe { self.insert_after_unchecked(k, v) };
3655+
}
3656+
}
3657+
35453658
/// Inserts a new key-value pair into the map in the gap that the
35463659
/// cursor is currently pointing to.
35473660
///

library/alloc/src/collections/btree/map/tests.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,16 @@ create_append_test!(test_append_239, 239);
21282128
#[cfg(not(miri))] // Miri is too slow
21292129
create_append_test!(test_append_1700, 1700);
21302130

2131+
// a inserts (0, 0)..(8, 8) to its own tree
2132+
// b inserts (5, 5 * 2)..($len, 2 * $len) to its own tree
2133+
// note that between a and b, there are duplicate keys
2134+
// between 5..min($len, 8), so on merge we add the values
2135+
// of these keys together
2136+
// we check that:
2137+
// - the merged tree 'a' has a length of max(8, $len)
2138+
// - all keys in 'a' have the correct value associated
2139+
// - removing and inserting an element into the merged
2140+
// tree 'a' still keeps it in valid tree form
21312141
macro_rules! create_merge_test {
21322142
($name:ident, $len:expr) => {
21332143
#[test]
@@ -2239,6 +2249,84 @@ fn test_append_ord_chaos() {
22392249
map2.check();
22402250
}
22412251

2252+
#[test]
2253+
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
2254+
fn test_merge_drop_leak() {
2255+
let a = CrashTestDummy::new(0);
2256+
let b = CrashTestDummy::new(1);
2257+
let c = CrashTestDummy::new(2);
2258+
let mut left = BTreeMap::new();
2259+
let mut right = BTreeMap::new();
2260+
left.insert(a.spawn(Panic::Never), ());
2261+
left.insert(b.spawn(Panic::Never), ());
2262+
left.insert(c.spawn(Panic::Never), ());
2263+
right.insert(b.spawn(Panic::InDrop), ()); // first duplicate key, dropped during merge
2264+
right.insert(c.spawn(Panic::Never), ());
2265+
2266+
catch_unwind(move || left.merge(right, |_, _, _| ())).unwrap_err();
2267+
assert_eq!(a.dropped(), 1); // this should not be dropped
2268+
assert_eq!(b.dropped(), 2); // key is dropped on panic
2269+
assert_eq!(c.dropped(), 2); // key is dropped on panic
2270+
}
2271+
2272+
#[test]
2273+
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
2274+
fn test_merge_conflict_drop_leak() {
2275+
let a = CrashTestDummy::new(0);
2276+
let a_val_left = CrashTestDummy::new(0);
2277+
2278+
let b = CrashTestDummy::new(1);
2279+
let b_val_left = CrashTestDummy::new(1);
2280+
let b_val_right = CrashTestDummy::new(1);
2281+
2282+
let c = CrashTestDummy::new(2);
2283+
let c_val_left = CrashTestDummy::new(2);
2284+
let c_val_right = CrashTestDummy::new(2);
2285+
2286+
let mut left = BTreeMap::new();
2287+
let mut right = BTreeMap::new();
2288+
2289+
left.insert(a.spawn(Panic::Never), a_val_left.spawn(Panic::Never));
2290+
left.insert(b.spawn(Panic::Never), b_val_left.spawn(Panic::Never));
2291+
left.insert(c.spawn(Panic::Never), c_val_left.spawn(Panic::Never));
2292+
right.insert(b.spawn(Panic::Never), b_val_right.spawn(Panic::Never));
2293+
right.insert(c.spawn(Panic::Never), c_val_right.spawn(Panic::Never));
2294+
2295+
// First key that conflicts should
2296+
catch_unwind(move || {
2297+
left.merge(right, |_, _, _| panic!("Panic in conflict function"));
2298+
assert_eq!(left.len(), 1); // only 1 entry should be left
2299+
})
2300+
.unwrap_err();
2301+
assert_eq!(a.dropped(), 1); // should not panic
2302+
assert_eq!(a_val_left.dropped(), 1); // should not panic
2303+
assert_eq!(b.dropped(), 2); // should drop from panic (conflict)
2304+
assert_eq!(b_val_left.dropped(), 1); // should be 2 were it not for Rust issue #47949
2305+
assert_eq!(b_val_right.dropped(), 1); // should be 2 were it not for Rust issue #47949
2306+
assert_eq!(c.dropped(), 2); // should drop from panic (conflict)
2307+
assert_eq!(c_val_left.dropped(), 1); // should be 2 were it not for Rust issue #47949
2308+
assert_eq!(c_val_right.dropped(), 1); // should be 2 were it not for Rust issue #47949
2309+
}
2310+
2311+
#[test]
2312+
fn test_merge_ord_chaos() {
2313+
let mut map1 = BTreeMap::new();
2314+
map1.insert(Cyclic3::A, ());
2315+
map1.insert(Cyclic3::B, ());
2316+
let mut map2 = BTreeMap::new();
2317+
map2.insert(Cyclic3::A, ());
2318+
map2.insert(Cyclic3::B, ());
2319+
map2.insert(Cyclic3::C, ()); // lands first, before A
2320+
map2.insert(Cyclic3::B, ()); // lands first, before C
2321+
map1.check();
2322+
map2.check(); // keys are not unique but still strictly ascending
2323+
assert_eq!(map1.len(), 2);
2324+
assert_eq!(map2.len(), 4);
2325+
map1.merge(map2, |_, _, _| ());
2326+
assert_eq!(map1.len(), 5);
2327+
map1.check();
2328+
}
2329+
22422330
fn rand_data(len: usize) -> Vec<(u32, u32)> {
22432331
let mut rng = DeterministicRng::new();
22442332
Vec::from_iter((0..len).map(|_| (rng.next(), rng.next())))
@@ -2695,9 +2783,15 @@ fn test_id_based_merge() {
26952783
rhs.insert(IdBased { id: 0, name: "rhs_k".to_string() }, "2".to_string());
26962784

26972785
lhs.merge(rhs, |_, mut lhs_val, rhs_val| {
2786+
// confirming that lhs_val comes from lhs tree,
2787+
// rhs_val comes from rhs tree
2788+
assert_eq!(lhs_val, String::from("1"));
2789+
assert_eq!(rhs_val, String::from("2"));
26982790
lhs_val.push_str(&rhs_val);
26992791
lhs_val
27002792
});
27012793

2702-
assert_eq!(lhs.pop_first().unwrap().0.name, "lhs_k".to_string());
2794+
let merged_kv_pair = lhs.pop_first().unwrap();
2795+
assert_eq!(merged_kv_pair.0.id, 0);
2796+
assert_eq!(merged_kv_pair.0.name, "lhs_k".to_string());
27032797
}

0 commit comments

Comments
 (0)