diff --git a/Cargo.lock b/Cargo.lock index 85bcb1b2..a2d8761e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,7 @@ dependencies = [ "parking_lot_core", "rayon", "serde", + "small-fixed-array", ] [[package]] @@ -230,6 +231,12 @@ dependencies = [ "syn", ] +[[package]] +name = "small-fixed-array" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2782e69a974e738ea1ef568e884347d8174a41789fe459ee73517c8acf8c9ea5" + [[package]] name = "smallvec" version = "1.11.0" diff --git a/Cargo.toml b/Cargo.toml index 602ea9c2..1bf0a738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ cfg-if = "1.0.0" rayon = { version = "1.7.0", optional = true } once_cell = "1.18.0" arbitrary = { version = "1.3.0", optional = true } +small-fixed-array = "0.4.0" [package.metadata.docs.rs] features = ["rayon", "raw-api", "serde"] diff --git a/src/iter.rs b/src/iter.rs index ce50e739..c4d44f42 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -25,7 +25,7 @@ use std::sync::Arc; /// ``` pub struct OwningIter { map: DashMap, - shard_i: usize, + shard_i: u32, current: Option>, } @@ -114,7 +114,7 @@ type GuardIterMut<'a, K, V, S> = ( /// ``` pub struct Iter<'a, K, V, S = RandomState, M = DashMap> { map: &'a M, - shard_i: usize, + shard_i: u32, current: Option>, } @@ -198,7 +198,7 @@ impl<'a, K: Eq + Hash, V, S: 'a + BuildHasher + Clone, M: Map<'a, K, V, S>> Iter /// ``` pub struct IterMut<'a, K, V, S = RandomState, M = DashMap> { map: &'a M, - shard_i: usize, + shard_i: u32, current: Option>, } diff --git a/src/lib.rs b/src/lib.rs index 34e5d8ba..c53a85c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,9 @@ use mapref::one::{Ref, RefMut}; use once_cell::sync::OnceCell; pub use read_only::ReadOnlyView; pub use set::DashSet; +use small_fixed_array::{FixedArray, ValidLength as _}; use std::collections::hash_map::RandomState; +use std::convert::{TryFrom as _, TryInto as _}; pub use t::Map; use try_result::TryResult; @@ -70,8 +72,8 @@ fn default_shard_amount() -> usize { }) } -fn ncb(shard_amount: usize) -> usize { - shard_amount.trailing_zeros() as usize +fn ncb(shard_amount: usize) -> u16 { + shard_amount.trailing_zeros() as _ } /// DashMap is an implementation of a concurrent associative array/hashmap in Rust. @@ -86,24 +88,28 @@ fn ncb(shard_amount: usize) -> usize { /// Documentation mentioning locking behaviour acts in the reference frame of the calling thread. /// This means that it is safe to ignore it across multiple threads. pub struct DashMap { - shift: usize, - shards: Box<[RwLock>]>, + shift: u16, + shards: FixedArray>>, hasher: S, } impl Clone for DashMap { fn clone(&self) -> Self { - let mut inner_shards = Vec::new(); + let mut inner_shards = Vec::with_capacity(self.shards.len() as usize); for shard in self.shards.iter() { let shard = shard.read(); - inner_shards.push(RwLock::new((*shard).clone())); } + let shards = inner_shards + .into_boxed_slice() + .try_into() + .unwrap_or_else(|_| panic!("size should not change so cannot overflow")); + Self { + shards, shift: self.shift, - shards: inner_shards.into_boxed_slice(), hasher: self.hasher.clone(), } } @@ -281,10 +287,13 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { let cps = capacity / shard_amount; - let shards = (0..shard_amount) + let shards: Box<[_]> = (0..shard_amount) .map(|_| RwLock::new(HashMap::with_capacity_and_hasher(cps, hasher.clone()))) .collect(); + let shards = FixedArray::try_from(shards) + .unwrap_or_else(|_| panic!("cannot store more than 4 billion shards")); + Self { shift, shards, @@ -295,11 +304,15 @@ 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 { + self.hash_u64(item) as 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() } cfg_if! { @@ -333,7 +346,7 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { /// use dashmap::SharedValue; /// /// let mut map = DashMap::::new(); - /// let shard_ind = map.determine_map(&42); + /// let shard_ind = map.determine_map(&42) as usize; /// map.shards_mut()[shard_ind].get_mut().insert(42, SharedValue::new("forty two")); /// assert_eq!(*map.get(&42).unwrap(), "forty two"); /// ``` @@ -348,7 +361,7 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { /// /// See [`DashMap::shards()`] and [`DashMap::shards_mut()`] for more information. pub fn into_shards(self) -> Box<[RwLock>]> { - self.shards + self.shards.into_boxed_slice() } } else { #[allow(dead_code)] @@ -363,7 +376,7 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { #[allow(dead_code)] pub(crate) fn into_shards(self) -> Box<[RwLock>]> { - self.shards + self.shards.into_boxed_slice() } } } @@ -385,7 +398,7 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { /// map.insert("coca-cola", 1.4); /// println!("coca-cola is stored in shard: {}", map.determine_map("coca-cola")); /// ``` - pub fn determine_map(&self, key: &Q) -> usize + pub fn determine_map(&self, key: &Q) -> u32 where K: Borrow, Q: Hash + Eq + ?Sized, @@ -411,16 +424,18 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { /// let key = "key"; /// let hash = map.hash_usize(&key); /// println!("hash is stored in shard: {}", map.determine_shard(hash)); + /// panic!() /// ``` - pub fn determine_shard(&self, hash: usize) -> usize { + pub fn determine_shard(&self, hash: usize) -> u32 { // Leave the high 7 bits for the HashBrown SIMD tag. - (hash << 7) >> self.shift + ((hash << 7) >> self.shift) as u32 } } else { - pub(crate) fn determine_shard(&self, hash: usize) -> usize { + pub(crate) fn determine_shard(&self, hash: usize) -> u32 { // Leave the high 7 bits for the HashBrown SIMD tag. - (hash << 7) >> self.shift + ((hash << 7) >> self.shift) as u32 + } } } @@ -875,44 +890,44 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { impl<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + BuildHasher + Clone> Map<'a, K, V, S> for DashMap { - fn _shard_count(&self) -> usize { + fn _shard_count(&self) -> u32 { self.shards.len() } - unsafe fn _get_read_shard(&'a self, i: usize) -> &'a HashMap { + unsafe fn _get_read_shard(&'a self, i: u32) -> &'a HashMap { debug_assert!(i < self.shards.len()); - &*self.shards.get_unchecked(i).data_ptr() + &*self.shards.get_unchecked(i.to_usize()).data_ptr() } - unsafe fn _yield_read_shard(&'a self, i: usize) -> RwLockReadGuard<'a, HashMap> { + unsafe fn _yield_read_shard(&'a self, i: u32) -> RwLockReadGuard<'a, HashMap> { debug_assert!(i < self.shards.len()); - self.shards.get_unchecked(i).read() + self.shards.get_unchecked(i.to_usize()).read() } - unsafe fn _yield_write_shard(&'a self, i: usize) -> RwLockWriteGuard<'a, HashMap> { + unsafe fn _yield_write_shard(&'a self, i: u32) -> RwLockWriteGuard<'a, HashMap> { debug_assert!(i < self.shards.len()); - self.shards.get_unchecked(i).write() + self.shards.get_unchecked(i.to_usize()).write() } unsafe fn _try_yield_read_shard( &'a self, - i: usize, + i: u32, ) -> Option>> { debug_assert!(i < self.shards.len()); - self.shards.get_unchecked(i).try_read() + self.shards.get_unchecked(i.to_usize()).try_read() } unsafe fn _try_yield_write_shard( &'a self, - i: usize, + i: u32, ) -> Option>> { debug_assert!(i < self.shards.len()); - self.shards.get_unchecked(i).try_write() + self.shards.get_unchecked(i.to_usize()).try_write() } fn _insert(&self, key: K, value: V) -> Option { diff --git a/src/rayon/map.rs b/src/rayon/map.rs index ab45e617..31dc4a29 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -5,6 +5,7 @@ use crate::{DashMap, HashMap}; use core::hash::{BuildHasher, Hash}; use rayon::iter::plumbing::UnindexedConsumer; use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator}; +use small_fixed_array::FixedArray; use std::collections::hash_map::RandomState; use std::sync::Arc; @@ -80,7 +81,7 @@ where } pub struct OwningIter { - pub(super) shards: Box<[RwLock>]>, + pub(super) shards: FixedArray>>, } impl ParallelIterator for OwningIter diff --git a/src/set.rs b/src/set.rs index 1a561770..8b060edd 100644 --- a/src/set.rs +++ b/src/set.rs @@ -159,7 +159,7 @@ impl<'a, K: 'a + Eq + Hash, S: BuildHasher + Clone> DashSet { /// set.insert("coca-cola"); /// println!("coca-cola is stored in shard: {}", set.determine_map("coca-cola")); /// ``` - pub fn determine_map(&self, key: &Q) -> usize + pub fn determine_map(&self, key: &Q) -> u32 where K: Borrow, Q: Hash + Eq + ?Sized, @@ -185,7 +185,7 @@ impl<'a, K: 'a + Eq + Hash, S: BuildHasher + Clone> DashSet { /// let hash = set.hash_usize(&key); /// println!("hash is stored in shard: {}", set.determine_shard(hash)); /// ``` - pub fn determine_shard(&self, hash: usize) -> usize { + pub fn determine_shard(&self, hash: usize) -> u32 { self.inner.determine_shard(hash) } } diff --git a/src/t.rs b/src/t.rs index 5e1fedcc..cf252c52 100644 --- a/src/t.rs +++ b/src/t.rs @@ -11,29 +11,29 @@ use core::hash::{BuildHasher, Hash}; /// Implementation detail that is exposed due to generic constraints in public types. pub trait Map<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + Clone + BuildHasher> { - fn _shard_count(&self) -> usize; + fn _shard_count(&self) -> u32; /// # Safety /// /// The index must not be out of bounds. - unsafe fn _get_read_shard(&'a self, i: usize) -> &'a HashMap; + unsafe fn _get_read_shard(&'a self, i: u32) -> &'a HashMap; /// # Safety /// /// The index must not be out of bounds. - unsafe fn _yield_read_shard(&'a self, i: usize) -> RwLockReadGuard<'a, HashMap>; + unsafe fn _yield_read_shard(&'a self, i: u32) -> RwLockReadGuard<'a, HashMap>; /// # Safety /// /// The index must not be out of bounds. - unsafe fn _yield_write_shard(&'a self, i: usize) -> RwLockWriteGuard<'a, HashMap>; + unsafe fn _yield_write_shard(&'a self, i: u32) -> RwLockWriteGuard<'a, HashMap>; /// # Safety /// /// The index must not be out of bounds. unsafe fn _try_yield_read_shard( &'a self, - i: usize, + i: u32, ) -> Option>>; /// # Safety @@ -41,7 +41,7 @@ pub trait Map<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + Clone + BuildHasher> { /// The index must not be out of bounds. unsafe fn _try_yield_write_shard( &'a self, - i: usize, + i: u32, ) -> Option>>; fn _insert(&self, key: K, value: V) -> Option; diff --git a/src/util.rs b/src/util.rs index d84e37db..54ba8ce0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,8 +3,8 @@ use core::cell::UnsafeCell; use core::{mem, ptr}; -pub const fn ptr_size_bits() -> usize { - mem::size_of::() * 8 +pub const fn ptr_size_bits() -> u16 { + (mem::size_of::() * 8) as _ } pub fn map_in_place_2 T>((k, v): (U, &mut T), f: F) {