From 62d9901c0f0cc33008bd9d881cbcf685b6aa6232 Mon Sep 17 00:00:00 2001 From: Stiopa Koltsov Date: Wed, 23 Mar 2022 23:52:40 +0000 Subject: [PATCH 1/2] Switch underlying hashmap to hashbrown Used in the following commit to avoid hashing key twice. --- Cargo.toml | 1 + src/iter.rs | 7 +++---- src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 549e4205..79072541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ send_guard = ["parking_lot/send_guard"] [dependencies] num_cpus = "1.13.1" parking_lot = "0.12.0" +hashbrown = "0.11.2" serde = { version = "1.0.136", optional = true, features = ["derive"] } cfg-if = "1.0.0" rayon = { version = "1.5.1", optional = true } diff --git a/src/iter.rs b/src/iter.rs index d21f560e..601ab409 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -6,7 +6,6 @@ use crate::{DashMap, HashMap}; use core::hash::{BuildHasher, Hash}; use core::mem; use parking_lot::{RwLockReadGuard, RwLockWriteGuard}; -use std::collections::hash_map; use std::collections::hash_map::RandomState; use std::sync::Arc; @@ -39,7 +38,7 @@ impl OwningIter { } } -type GuardOwningIter = hash_map::IntoIter>; +type GuardOwningIter = hashbrown::hash_map::IntoIter>; impl Iterator for OwningIter { type Item = (K, V); @@ -93,12 +92,12 @@ where type GuardIter<'a, K, V, S> = ( Arc>>, - hash_map::Iter<'a, K, SharedValue>, + hashbrown::hash_map::Iter<'a, K, SharedValue>, ); type GuardIterMut<'a, K, V, S> = ( Arc>>, - hash_map::IterMut<'a, K, SharedValue>, + hashbrown::hash_map::IterMut<'a, K, SharedValue>, ); /// Iterator over a DashMap yielding immutable references. diff --git a/src/lib.rs b/src/lib.rs index 2ebffe9e..b2e81cef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ cfg_if! { } } -pub(crate) type HashMap = std::collections::HashMap, S>; +pub(crate) type HashMap = hashbrown::HashMap, S>; fn default_shard_amount() -> usize { (num_cpus::get() * 4).next_power_of_two() From c20f57fe22e139bd35716129f9020b36835f162e Mon Sep 17 00:00:00 2001 From: Stiopa Koltsov Date: Thu, 24 Mar 2022 01:44:49 +0000 Subject: [PATCH 2/2] Hash key only once in map operations Hashbrown `HashMap` provides API to work with prehashed keys. --- src/lib.rs | 120 +++++++++++++++++++++++++++-------------------- src/read_only.rs | 27 +++++++---- 2 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b2e81cef..02623629 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -271,14 +271,18 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { } } - /// Hash a given item to produce a usize. - /// Uses the provided or default HashBuilder. - pub fn hash_usize(&self, item: &T) -> usize { + fn hash_u64(&self, item: &T) -> u64 { let mut hasher = self.hasher.build_hasher(); item.hash(&mut hasher); - hasher.finish() as usize + hasher.finish() + } + + /// Hash a given item to produce a usize. + /// Uses the provided or default HashBuilder. + pub fn hash_usize(&self, item: &T) -> usize { + self.hash_u64(item) as usize } cfg_if! { @@ -329,8 +333,8 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); - self.determine_shard(hash) + let hash = self.hash_u64(&key); + self.determine_shard(hash as usize) } } } @@ -837,15 +841,21 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> } fn _insert(&self, key: K, value: V) -> Option { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let mut shard = unsafe { self._yield_write_shard(idx) }; - shard - .insert(key, SharedValue::new(value)) - .map(|v| v.into_inner()) + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &key) { + hashbrown::hash_map::RawEntryMut::Occupied(mut occupied) => { + Some(occupied.insert(SharedValue::new(value)).into_inner()) + } + hashbrown::hash_map::RawEntryMut::Vacant(vacant) => { + vacant.insert(key, SharedValue::new(value)); + None + } + } } fn _remove(&self, key: &Q) -> Option<(K, V)> @@ -853,13 +863,19 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let mut shard = unsafe { self._yield_write_shard(idx) }; - shard.remove_entry(key).map(|(k, v)| (k, v.into_inner())) + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, key) { + hashbrown::hash_map::RawEntryMut::Occupied(entry) => { + let (k, v) = entry.remove_entry(); + Some((k, v.into_inner())) + } + hashbrown::hash_map::RawEntryMut::Vacant(_) => None, + } } fn _remove_if(&self, key: &Q, f: impl FnOnce(&K, &V) -> bool) -> Option<(K, V)> @@ -867,20 +883,22 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let mut shard = unsafe { self._yield_write_shard(idx) }; - if let Some((k, v)) = shard.get_key_value(key) { - if f(k, v.get()) { - shard.remove_entry(key).map(|(k, v)| (k, v.into_inner())) - } else { - None + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, key) { + hashbrown::hash_map::RawEntryMut::Occupied(occupied) => { + if f(&occupied.key(), &occupied.get().get()) { + let (k, v) = occupied.remove_entry(); + Some((k, v.into_inner())) + } else { + None + } } - } else { - None + hashbrown::hash_map::RawEntryMut::Vacant(_) => None, } } @@ -889,25 +907,23 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let mut shard = unsafe { self._yield_write_shard(idx) }; - if let Some((kptr, vptr)) = shard.get_key_value(&key) { - unsafe { - let kptr: *const K = kptr; - let vptr: *mut V = vptr.as_ptr(); - - if f(&*kptr, &mut *vptr) { - shard.remove_entry(key).map(|(k, v)| (k, v.into_inner())) + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, key) { + hashbrown::hash_map::RawEntryMut::Occupied(mut occupied) => { + let (k, v) = occupied.get_key_value_mut(); + if f(k, v.get_mut()) { + let (k, v) = occupied.remove_entry(); + Some((k, v.into_inner())) } else { None } } - } else { - None + hashbrown::hash_map::RawEntryMut::Vacant(_) => None, } } @@ -924,13 +940,13 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let shard = unsafe { self._yield_read_shard(idx) }; - if let Some((kptr, vptr)) = shard.get_key_value(key) { + if let Some((kptr, vptr)) = shard.raw_entry().from_key_hashed_nocheck(hash, key) { unsafe { let kptr: *const K = kptr; let vptr: *const V = vptr.get(); @@ -946,13 +962,13 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let shard = unsafe { self._yield_write_shard(idx) }; - if let Some((kptr, vptr)) = shard.get_key_value(key) { + if let Some((kptr, vptr)) = shard.raw_entry().from_key_hashed_nocheck(hash, key) { unsafe { let kptr: *const K = kptr; let vptr: *mut V = vptr.as_ptr(); @@ -968,16 +984,16 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let shard = match unsafe { self._try_yield_read_shard(idx) } { Some(shard) => shard, None => return TryResult::Locked, }; - if let Some((kptr, vptr)) = shard.get_key_value(key) { + if let Some((kptr, vptr)) = shard.raw_entry().from_key_hashed_nocheck(hash, key) { unsafe { let kptr: *const K = kptr; let vptr: *const V = vptr.get(); @@ -993,16 +1009,16 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let shard = match unsafe { self._try_yield_write_shard(idx) } { Some(shard) => shard, None => return TryResult::Locked, }; - if let Some((kptr, vptr)) = shard.get_key_value(key) { + if let Some((kptr, vptr)) = shard.raw_entry().from_key_hashed_nocheck(hash, key) { unsafe { let kptr: *const K = kptr; let vptr: *mut V = vptr.as_ptr(); @@ -1061,13 +1077,13 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> } fn _entry(&'a self, key: K) -> Entry<'a, K, V, S> { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let shard = unsafe { self._yield_write_shard(idx) }; - if let Some((kptr, vptr)) = shard.get_key_value(&key) { + if let Some((kptr, vptr)) = shard.raw_entry().from_key_hashed_nocheck(hash, &key) { unsafe { let kptr: *const K = kptr; let vptr: *mut V = vptr.as_ptr(); @@ -1079,16 +1095,16 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> } fn _try_entry(&'a self, key: K) -> Option> { - let hash = self.hash_usize(&key); + let hash = self.hash_u64(&key); - let idx = self.determine_shard(hash); + let idx = self.determine_shard(hash as usize); let shard = match unsafe { self._try_yield_write_shard(idx) } { Some(shard) => shard, None => return None, }; - if let Some((kptr, vptr)) = shard.get_key_value(&key) { + if let Some((kptr, vptr)) = shard.raw_entry().from_key_hashed_nocheck(hash, &key) { unsafe { let kptr: *const K = kptr; let vptr: *mut V = vptr.as_ptr(); diff --git a/src/read_only.rs b/src/read_only.rs index b0e7a65e..c874698d 100644 --- a/src/read_only.rs +++ b/src/read_only.rs @@ -59,13 +59,16 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> ReadOnlyView K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.map.hash_usize(&key); + let hash = self.map.hash_u64(&key); - let idx = self.map.determine_shard(hash); + let idx = self.map.determine_shard(hash as usize); let shard = unsafe { self.map._get_read_shard(idx) }; - shard.contains_key(key) + shard + .raw_entry() + .from_key_hashed_nocheck(hash, key) + .is_some() } /// Returns a reference to the value corresponding to the key. @@ -74,13 +77,16 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> ReadOnlyView K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.map.hash_usize(&key); + let hash = self.map.hash_u64(&key); - let idx = self.map.determine_shard(hash); + let idx = self.map.determine_shard(hash as usize); let shard = unsafe { self.map._get_read_shard(idx) }; - shard.get(key).map(|v| v.get()) + shard + .raw_entry() + .from_key_hashed_nocheck(hash, key) + .map(|(_k, v)| v.get()) } /// Returns the key-value pair corresponding to the supplied key. @@ -89,13 +95,16 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> ReadOnlyView K: Borrow, Q: Hash + Eq + ?Sized, { - let hash = self.map.hash_usize(&key); + let hash = self.map.hash_u64(&key); - let idx = self.map.determine_shard(hash); + let idx = self.map.determine_shard(hash as usize); let shard = unsafe { self.map._get_read_shard(idx) }; - shard.get_key_value(key).map(|(k, v)| (k, v.get())) + shard + .raw_entry() + .from_key_hashed_nocheck(hash, key) + .map(|(k, v)| (k, v.get())) } fn shard_read_iter(&'a self) -> impl Iterator> + 'a {