Rollup merge of #48035 - technicalguy:Early-exit-empty-hashmap-38880, r=arthurprs
Early exit for empty HashMap (issue #38880) Addresses issue #38880 by checking if the HashMap is empty before computing the value of the hash. Before (integer keys) ``` running 4 tests test empty_once ... bench: 13 ns/iter (+/- 0) test empty_100 ... bench: 1,367 ns/iter (+/- 35) test exist_once ... bench: 14 ns/iter (+/- 0) test exist_100 ... bench: 1,518 ns/iter (+/- 40) ``` After ``` running 4 tests test empty_once ... bench: 2 ns/iter (+/- 0) test empty_100 ... bench: 221 ns/iter (+/- 0) test exist_once ... bench: 15 ns/iter (+/- 0) test exist_100 ... bench: 1,515 ns/iter (+/- 92) ``` When the HashMap is not empty, the performance remains the same, and when it is empty the performance is significantly improved.
This commit is contained in:
@@ -398,8 +398,9 @@ pub struct HashMap<K, V, S = RandomState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a pre-hashed key.
|
/// Search for a pre-hashed key.
|
||||||
|
/// If you don't already know the hash, use search or search_mut instead
|
||||||
#[inline]
|
#[inline]
|
||||||
fn search_hashed<K, V, M, F>(table: M, hash: SafeHash, mut is_match: F) -> InternalEntry<K, V, M>
|
fn search_hashed<K, V, M, F>(table: M, hash: SafeHash, is_match: F) -> InternalEntry<K, V, M>
|
||||||
where M: Deref<Target = RawTable<K, V>>,
|
where M: Deref<Target = RawTable<K, V>>,
|
||||||
F: FnMut(&K) -> bool
|
F: FnMut(&K) -> bool
|
||||||
{
|
{
|
||||||
@@ -410,6 +411,18 @@ fn search_hashed<K, V, M, F>(table: M, hash: SafeHash, mut is_match: F) -> Inter
|
|||||||
return InternalEntry::TableIsEmpty;
|
return InternalEntry::TableIsEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
search_hashed_nonempty(table, hash, is_match)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for a pre-hashed key when the hash map is known to be non-empty.
|
||||||
|
#[inline]
|
||||||
|
fn search_hashed_nonempty<K, V, M, F>(table: M, hash: SafeHash, mut is_match: F)
|
||||||
|
-> InternalEntry<K, V, M>
|
||||||
|
where M: Deref<Target = RawTable<K, V>>,
|
||||||
|
F: FnMut(&K) -> bool
|
||||||
|
{
|
||||||
|
// Do not check the capacity as an extra branch could slow the lookup.
|
||||||
|
|
||||||
let size = table.size();
|
let size = table.size();
|
||||||
let mut probe = Bucket::new(table, hash);
|
let mut probe = Bucket::new(table, hash);
|
||||||
let mut displacement = 0;
|
let mut displacement = 0;
|
||||||
@@ -543,24 +556,36 @@ impl<K, V, S> HashMap<K, V, S>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a key, yielding the index if it's found in the hashtable.
|
/// Search for a key, yielding the index if it's found in the hashtable.
|
||||||
/// If you already have the hash for the key lying around, use
|
/// If you already have the hash for the key lying around, or if you need an
|
||||||
/// search_hashed.
|
/// InternalEntry, use search_hashed or search_hashed_nonempty.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry<K, V, &'a RawTable<K, V>>
|
fn search<'a, Q: ?Sized>(&'a self, q: &Q)
|
||||||
|
-> Option<FullBucket<K, V, &'a RawTable<K, V>>>
|
||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Eq + Hash
|
Q: Eq + Hash
|
||||||
{
|
{
|
||||||
|
if self.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let hash = self.make_hash(q);
|
let hash = self.make_hash(q);
|
||||||
search_hashed(&self.table, hash, |k| q.eq(k.borrow()))
|
search_hashed_nonempty(&self.table, hash, |k| q.eq(k.borrow()))
|
||||||
|
.into_occupied_bucket()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> InternalEntry<K, V, &'a mut RawTable<K, V>>
|
fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q)
|
||||||
|
-> Option<FullBucket<K, V, &'a mut RawTable<K, V>>>
|
||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Eq + Hash
|
Q: Eq + Hash
|
||||||
{
|
{
|
||||||
|
if self.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let hash = self.make_hash(q);
|
let hash = self.make_hash(q);
|
||||||
search_hashed(&mut self.table, hash, |k| q.eq(k.borrow()))
|
search_hashed_nonempty(&mut self.table, hash, |k| q.eq(k.borrow()))
|
||||||
|
.into_occupied_bucket()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The caller should ensure that invariants by Robin Hood Hashing hold
|
// The caller should ensure that invariants by Robin Hood Hashing hold
|
||||||
@@ -1118,7 +1143,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Hash + Eq
|
Q: Hash + Eq
|
||||||
{
|
{
|
||||||
self.search(k).into_occupied_bucket().map(|bucket| bucket.into_refs().1)
|
self.search(k).map(|bucket| bucket.into_refs().1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the map contains a value for the specified key.
|
/// Returns true if the map contains a value for the specified key.
|
||||||
@@ -1145,7 +1170,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Hash + Eq
|
Q: Hash + Eq
|
||||||
{
|
{
|
||||||
self.search(k).into_occupied_bucket().is_some()
|
self.search(k).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the value corresponding to the key.
|
/// Returns a mutable reference to the value corresponding to the key.
|
||||||
@@ -1174,7 +1199,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Hash + Eq
|
Q: Hash + Eq
|
||||||
{
|
{
|
||||||
self.search_mut(k).into_occupied_bucket().map(|bucket| bucket.into_mut_refs().1)
|
self.search_mut(k).map(|bucket| bucket.into_mut_refs().1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a key-value pair into the map.
|
/// Inserts a key-value pair into the map.
|
||||||
@@ -1234,11 +1259,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Hash + Eq
|
Q: Hash + Eq
|
||||||
{
|
{
|
||||||
if self.table.size() == 0 {
|
self.search_mut(k).map(|bucket| pop_internal(bucket).1)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.search_mut(k).into_occupied_bucket().map(|bucket| pop_internal(bucket).1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a key from the map, returning the stored key and value if the
|
/// Removes a key from the map, returning the stored key and value if the
|
||||||
@@ -1269,12 +1290,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||||||
where K: Borrow<Q>,
|
where K: Borrow<Q>,
|
||||||
Q: Hash + Eq
|
Q: Hash + Eq
|
||||||
{
|
{
|
||||||
if self.table.size() == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.search_mut(k)
|
self.search_mut(k)
|
||||||
.into_occupied_bucket()
|
|
||||||
.map(|bucket| {
|
.map(|bucket| {
|
||||||
let (k, v, _) = pop_internal(bucket);
|
let (k, v, _) = pop_internal(bucket);
|
||||||
(k, v)
|
(k, v)
|
||||||
@@ -2632,15 +2648,11 @@ impl<K, S, Q: ?Sized> super::Recover<Q> for HashMap<K, (), S>
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get(&self, key: &Q) -> Option<&K> {
|
fn get(&self, key: &Q) -> Option<&K> {
|
||||||
self.search(key).into_occupied_bucket().map(|bucket| bucket.into_refs().0)
|
self.search(key).map(|bucket| bucket.into_refs().0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&mut self, key: &Q) -> Option<K> {
|
fn take(&mut self, key: &Q) -> Option<K> {
|
||||||
if self.table.size() == 0 {
|
self.search_mut(key).map(|bucket| pop_internal(bucket).0)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.search_mut(key).into_occupied_bucket().map(|bucket| pop_internal(bucket).0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
Reference in New Issue
Block a user