diff --git a/Cargo.toml b/Cargo.toml index b7ebf0e4..0d60720f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ maintenance = { status = "experimental" } crossbeam = "0.7" parking_lot = "0.10" rand = "0.7" +num_cpus = "1.12.0" diff --git a/src/lib.rs b/src/lib.rs index 17ea09d1..a30df88c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,7 +200,11 @@ use node::*; use crossbeam::epoch::{Atomic, Guard, Owned, Shared}; use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash}; -use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; +use std::iter::FromIterator; +use std::sync::{ + atomic::{AtomicIsize, AtomicUsize, Ordering}, + Once, +}; /// The largest possible table capacity. This value must be /// exactly 1<<30 to stay within Java array allocation and indexing @@ -238,6 +242,9 @@ const MAX_RESIZERS: isize = (1 << (32 - RESIZE_STAMP_BITS)) - 1; /// The bit shift for recording size stamp in `size_ctl`. const RESIZE_STAMP_SHIFT: usize = 32 - RESIZE_STAMP_BITS; +static NCPU_INITIALIZER: Once = Once::new(); +static NCPU: AtomicUsize = AtomicUsize::new(0); + /// Iterator types. pub mod iter; use iter::*; @@ -304,12 +311,11 @@ where /// Creates a new, empty map with an initial table size accommodating the specified number of /// elements without the need to dynamically resize. - /// - /// # Panics - /// - /// If the given capacity is 0. pub fn with_capacity(n: usize) -> Self { - assert_ne!(n, 0); + if n == 0 { + return Self::new(); + } + let mut m = Self::new(); let size = (1.0 + (n as f64) / LOAD_FACTOR) as usize; // NOTE: tableSizeFor in Java @@ -444,6 +450,7 @@ where } } + #[inline] /// Maps `key` to `value` in this table. /// /// The value can be retrieved by calling [`get`] with a key that is equal to the original key. @@ -643,6 +650,12 @@ where } } + fn put_all<'g, I: Iterator>(&self, iter: I, guard: &'g Guard) { + for (key, value) in iter { + self.put(key, value, false, guard); + } + } + fn help_transfer<'g>( &'g self, table: Shared<'g, Table>, @@ -769,9 +782,10 @@ where // this references is still active (marked by the guard), so the target of the references // won't be dropped while the guard remains active. let n = unsafe { table.deref() }.bins.len(); + let ncpu = num_cpus(); - // TODO: use num_cpus to help determine stride - let stride = MIN_TRANSFER_STRIDE; + let stride = if ncpu > 1 { (n >> 3) / ncpu } else { n }; + let stride = std::cmp::max(stride as isize, MIN_TRANSFER_STRIDE); if next_table.is_null() { // we are initiating a resize @@ -1225,6 +1239,18 @@ where let node_iter = NodeIter::new(table, guard); Values { node_iter, guard } } + + #[inline] + /// Returns the number of entries in the map. + pub fn len(&self) -> usize { + self.count.load(Ordering::Relaxed) + } + + #[inline] + /// Returns `true` if the map is empty. Otherwise returns `false`. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } } impl Drop for FlurryHashMap { @@ -1251,6 +1277,82 @@ impl Drop for FlurryHashMap { } } +impl Extend<(K, V)> for &FlurryHashMap +where + K: Sync + Send + Clone + Hash + Eq, + V: Sync + Send, + S: BuildHasher, +{ + #[inline] + // TODO: Implement Java's `tryPresize` method to pre-allocate space for + // the incoming entries + // NOTE: `hashbrown::HashMap::extend` provides some good guidance on how + // to choose the presizing value based on the iterator lower bound. + fn extend>(&mut self, iter: T) { + let guard = crossbeam::epoch::pin(); + (*self).put_all(iter.into_iter(), &guard); + } +} + +impl<'a, K, V, S> Extend<(&'a K, &'a V)> for &FlurryHashMap +where + K: Sync + Send + Copy + Hash + Eq, + V: Sync + Send + Copy, + S: BuildHasher, +{ + #[inline] + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|(&key, &value)| (key, value))); + } +} + +impl FromIterator<(K, V)> for FlurryHashMap +where + K: Sync + Send + Clone + Hash + Eq, + V: Sync + Send, +{ + fn from_iter>(iter: T) -> Self { + let mut iter = iter.into_iter(); + + if let Some((key, value)) = iter.next() { + // safety: we own `map`, so it's not concurrently accessed by + // anyone else at this point. + let guard = unsafe { crossbeam::epoch::unprotected() }; + + let (lower, _) = iter.size_hint(); + let map = Self::with_capacity(lower.saturating_add(1)); + + map.put(key, value, false, &guard); + map.put_all(iter, &guard); + map + } else { + Self::new() + } + } +} + +impl<'a, K, V> FromIterator<(&'a K, &'a V)> for FlurryHashMap +where + K: Sync + Send + Copy + Hash + Eq, + V: Sync + Send + Copy, +{ + #[inline] + fn from_iter>(iter: T) -> Self { + Self::from_iter(iter.into_iter().map(|(&k, &v)| (k, v))) + } +} + +impl<'a, K, V> FromIterator<&'a (K, V)> for FlurryHashMap +where + K: Sync + Send + Copy + Hash + Eq, + V: Sync + Send + Copy, +{ + #[inline] + fn from_iter>(iter: T) -> Self { + Self::from_iter(iter.into_iter().map(|&(k, v)| (k, v))) + } +} + #[derive(Debug)] struct Table { bins: Box<[Atomic>]>, @@ -1360,6 +1462,14 @@ impl Table { } } +#[inline] +/// Returns the number of physical CPUs in the machine (_O(1)_). +fn num_cpus() -> usize { + NCPU_INITIALIZER.call_once(|| NCPU.store(num_cpus::get_physical(), Ordering::Relaxed)); + + NCPU.load(Ordering::Relaxed) +} + /// It's kind of stupid, but apparently there is no way to write a regular `#[test]` that is _not_ /// supposed to compile without pulling in `compiletest` as a dependency. See rust-lang/rust#12335. /// But it _is_ possible to write `compile_test` tests as doctests, sooooo: diff --git a/tests/jsr166.rs b/tests/jsr166.rs index 88fded41..1d718fc8 100644 --- a/tests/jsr166.rs +++ b/tests/jsr166.rs @@ -1,4 +1,36 @@ use flurry::*; +use std::iter::FromIterator; + +const ITER: [(usize, &'static str); 5] = [(1, "A"), (2, "B"), (3, "C"), (4, "D"), (5, "E")]; + +#[test] +fn test_from_iter() { + let guard = unsafe { crossbeam::epoch::unprotected() }; + let map1 = from_iter_contron(); + let map2 = FlurryHashMap::from_iter(ITER.iter()); + + // TODO: improve when `Map: Eq` + let mut fst: Vec<_> = map1.iter(&guard).collect(); + let mut snd: Vec<_> = map2.iter(&guard).collect(); + fst.sort(); + snd.sort(); + + assert_eq!(fst, snd); +} + +fn from_iter_contron() -> FlurryHashMap { + let guard = unsafe { crossbeam::epoch::unprotected() }; + let map = FlurryHashMap::with_capacity(5); + assert!(map.is_empty()); + + for (key, value) in &ITER { + map.insert(*key, *value, &guard); + } + + assert!(!map.is_empty()); + assert_eq!(ITER.len(), map.len()); + map +} fn map5() -> FlurryHashMap { let map = FlurryHashMap::new();