Skip to content

Commit 1f12721

Browse files
committed
Improve memory reservation for insert_entry
In `core::RefMut::insert_unique`, used by `insert_entry` and others, we were calling `reserve_entries` *before* the table insert, which defeats the goal of matching capacities. We can't directly call that after table insert though, because we'll be holding an `OccupiedEntry` that prevents looking at the table itself. Instead, this code path now uses a more typical doubling growth on the entries `Vec` itself, but still enhanced by considering `MAX_ENTRIES_CAPACITY` as well.
1 parent 31c9862 commit 1f12721

File tree

1 file changed

+22
-18
lines changed

1 file changed

+22
-18
lines changed

src/map/core.rs

+22-18
Original file line numberDiff line numberDiff line change
@@ -512,40 +512,44 @@ impl<K, V> IndexMapCore<K, V> {
512512
}
513513
}
514514

515+
/// Reserve entries capacity, rounded up to match the indices (via `try_capacity`).
516+
fn reserve_entries<K, V>(entries: &mut Entries<K, V>, additional: usize, try_capacity: usize) {
517+
// Use a soft-limit on the maximum capacity, but if the caller explicitly
518+
// requested more, do it and let them have the resulting panic.
519+
let try_capacity = try_capacity.min(IndexMapCore::<K, V>::MAX_ENTRIES_CAPACITY);
520+
let try_add = try_capacity - entries.len();
521+
if try_add > additional && entries.try_reserve_exact(try_add).is_ok() {
522+
return;
523+
}
524+
entries.reserve_exact(additional);
525+
}
526+
515527
impl<'a, K, V> RefMut<'a, K, V> {
516528
#[inline]
517529
fn new(indices: &'a mut Indices, entries: &'a mut Entries<K, V>) -> Self {
518530
Self { indices, entries }
519531
}
520532

521533
/// Reserve entries capacity, rounded up to match the indices
534+
#[inline]
522535
fn reserve_entries(&mut self, additional: usize) {
523-
// Use a soft-limit on the maximum capacity, but if the caller explicitly
524-
// requested more, do it and let them have the resulting panic.
525-
let new_capacity = Ord::min(
526-
self.indices.capacity(),
527-
IndexMapCore::<K, V>::MAX_ENTRIES_CAPACITY,
528-
);
529-
let try_add = new_capacity - self.entries.len();
530-
if try_add > additional && self.entries.try_reserve_exact(try_add).is_ok() {
531-
return;
532-
}
533-
self.entries.reserve_exact(additional);
536+
reserve_entries(self.entries, additional, self.indices.capacity());
534537
}
535538

536539
/// Insert a key-value pair in `entries`,
537540
/// *without* checking whether it already exists.
538-
fn insert_unique(mut self, hash: HashValue, key: K, value: V) -> OccupiedEntry<'a, K, V> {
539-
if self.entries.len() == self.entries.capacity() {
540-
// Reserve our own capacity synced to the indices,
541-
// rather than letting `Vec::push` just double it.
542-
self.reserve_entries(1);
543-
}
541+
fn insert_unique(self, hash: HashValue, key: K, value: V) -> OccupiedEntry<'a, K, V> {
544542
let i = self.indices.len();
543+
debug_assert_eq!(i, self.entries.len());
545544
let entry = self
546545
.indices
547546
.insert_unique(hash.get(), i, get_hash(self.entries));
548-
debug_assert_eq!(i, self.entries.len());
547+
if self.entries.len() == self.entries.capacity() {
548+
// We can't call `indices.capacity()` while this `entry` has borrowed it, so we'll have
549+
// to amortize growth on our own. It's still an improvement over the basic `Vec::push`
550+
// doubling though, since we also consider `MAX_ENTRIES_CAPACITY`.
551+
reserve_entries(self.entries, 1, 2 * self.entries.capacity());
552+
}
549553
self.entries.push(Bucket { hash, key, value });
550554
OccupiedEntry::new(self.entries, entry)
551555
}

0 commit comments

Comments
 (0)