diff --git a/Cargo.toml b/Cargo.toml index b0e17d0..b7e9990 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,10 @@ travis-ci = { repository = "jonhoo/rust-evmap" } maintenance = { status = "passively-maintained" } [dependencies] +hashbrown = { version = "0.1.8", optional = true } +smallvec = { version = "0.6.7", optional = true } + +[features] +default = ["hashbrown", "smallvec"] +nightly_hashbrown = ["hashbrown/nightly"] +nightly_smallvec = ["smallvec/union"] \ No newline at end of file diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 049febb..cc70a8c 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -9,6 +9,13 @@ chashmap = "2.1.0" clap = "2.20.3" zipf = "0.2.0" rand = "0.3" +parking_lot = "0.7.1" [profile.release] -debug = true +lto = true +opt-level = 3 +codegen-units = 1 +debug = false + +[features] +nightly = ["evmap/nightly_hashbrown", "evmap/nightly_smallvec"] diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 44191e9..49c859b 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -4,6 +4,7 @@ extern crate clap; extern crate evmap; extern crate rand; extern crate zipf; +extern crate parking_lot; use chashmap::CHashMap; use std::collections::HashMap; @@ -87,7 +88,7 @@ fn main() { // first, benchmark Arc> if matches.is_present("compare") { let map: HashMap = HashMap::with_capacity(5_000_000); - let map = sync::Arc::new(sync::RwLock::new(map)); + let map = sync::Arc::new(parking_lot::RwLock::new(map)); let start = time::Instant::now(); let end = start + dur; join.extend((0..readers).into_iter().map(|_| { @@ -135,7 +136,7 @@ fn main() { let (r, w) = evmap::Options::default() .with_capacity(5_000_000) .construct(); - let w = sync::Arc::new(sync::Mutex::new((w, 0, refresh))); + let w = sync::Arc::new(parking_lot::Mutex::new((w, 0, refresh))); let start = time::Instant::now(); let end = start + dur; join.extend((0..readers).into_iter().map(|_| { @@ -206,19 +207,19 @@ impl Backend for sync::Arc> { } } -impl Backend for sync::Arc>> { +impl Backend for sync::Arc>> { fn b_get(&self, key: u64) -> u64 { - self.read().unwrap().get(&key).map(|&v| v).unwrap_or(0) + self.read().get(&key).map(|&v| v).unwrap_or(0) } fn b_put(&mut self, key: u64, value: u64) { - self.write().unwrap().insert(key, value); + self.write().insert(key, value); } } enum EvHandle { Read(evmap::ReadHandle), - Write(sync::Arc, usize, usize)>>), + Write(sync::Arc, usize, usize)>>), } impl Backend for EvHandle { @@ -232,7 +233,7 @@ impl Backend for EvHandle { fn b_put(&mut self, key: u64, value: u64) { if let EvHandle::Write(ref w) = *self { - let mut w = w.lock().unwrap(); + let mut w = w.lock(); w.0.update(key, value); w.1 += 1; if w.1 == w.2 { diff --git a/src/inner.rs b/src/inner.rs index 8490343..4f9eefd 100644 --- a/src/inner.rs +++ b/src/inner.rs @@ -1,13 +1,24 @@ -use std::collections::HashMap; use std::hash::{BuildHasher, Hash}; use std::sync::{atomic, Arc, Mutex}; +#[cfg(not(feature = "hashbrown"))] +use std::collections::HashMap; + +#[cfg(feature = "hashbrown")] +use hashbrown::HashMap; + +#[cfg(not(feature = "smallvec"))] +pub(crate) type Values = Vec; + +#[cfg(feature = "smallvec")] +pub(crate) type Values = smallvec::SmallVec<[T; 1]>; + pub(crate) struct Inner where K: Eq + Hash, S: BuildHasher, { - pub(crate) data: HashMap, S>, + pub(crate) data: HashMap, S>, pub(crate) epochs: Arc>>>, pub(crate) meta: M, ready: bool, diff --git a/src/lib.rs b/src/lib.rs index e8add5f..eaaa231 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,6 +184,16 @@ //! #![deny(missing_docs)] +#[cfg(feature = "hashbrown")] +extern crate hashbrown; + +#[cfg(feature = "smallvec")] +extern crate smallvec; + +/// Re-export default FxHash hash builder from `hashbrown` +#[cfg(feature = "hashbrown")] +pub type FxHashBuilder = hashbrown::hash_map::DefaultHashBuilder; + use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash}; @@ -295,6 +305,8 @@ where } /// Create an empty eventually consistent map. +/// +/// Use the [`Options`](./struct.Options.html) builder for more control over initialization. #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] pub fn new() -> ( ReadHandle, @@ -308,6 +320,8 @@ where } /// Create an empty eventually consistent map with meta information. +/// +/// Use the [`Options`](./struct.Options.html) builder for more control over initialization. #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] pub fn with_meta( meta: M, @@ -323,6 +337,26 @@ where Options::default().with_meta(meta).construct() } +/// Create an empty eventually consistent map with meta information and custom hasher. +/// +/// Use the [`Options`](./struct.Options.html) builder for more control over initialization. +#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] +pub fn with_hasher( + meta: M, + hasher: S, +) -> (ReadHandle, WriteHandle) +where + K: Eq + Hash + Clone, + V: Eq + ShallowCopy, + M: 'static + Clone, + S: BuildHasher + Clone, +{ + Options::default() + .with_hasher(hasher) + .with_meta(meta) + .construct() +} + // test that ReadHandle isn't Sync // waiting on https://github.com/rust-lang/rust/issues/17606 //#[test] diff --git a/src/read.rs b/src/read.rs index 728333f..9209523 100644 --- a/src/read.rs +++ b/src/read.rs @@ -59,9 +59,11 @@ where { // tell writer about our epoch tracker let epoch = sync::Arc::new(atomic::AtomicUsize::new(0)); + inner.epochs.lock().unwrap().push(Arc::clone(&epoch)); let store = Box::into_raw(Box::new(inner)); + ReadHandle { epoch: epoch, my_epoch: atomic::AtomicUsize::new(0), @@ -177,7 +179,8 @@ where } else { inner.data.get(key).map(move |v| then(&**v)) } - }).unwrap_or(None) + }) + .unwrap_or(None) } /// Applies a function to the values corresponding to the key, and returns the result alongside @@ -206,7 +209,8 @@ where let res = (res, inner.meta.clone()); Some(res) } - }).unwrap_or(None) + }) + .unwrap_or(None) } /// If the writer has destroyed this map, this method will return true. @@ -252,6 +256,7 @@ where { self.with_handle(move |inner| { Collector::from_iter(inner.data.iter().map(|(k, vs)| f(k, &vs[..]))) - }).unwrap_or(Collector::from_iter(iter::empty())) + }) + .unwrap_or_else(|| Collector::from_iter(iter::empty())) } } diff --git a/src/write.rs b/src/write.rs index e836da5..0ddd182 100644 --- a/src/write.rs +++ b/src/write.rs @@ -1,5 +1,5 @@ use super::{Operation, ShallowCopy}; -use inner::Inner; +use inner::{Inner, Values}; use read::ReadHandle; use std::collections::hash_map::RandomState; @@ -8,6 +8,12 @@ use std::sync::atomic; use std::sync::{Arc, MutexGuard}; use std::{mem, thread}; +#[cfg(feature = "hashbrown")] +use hashbrown::hash_map::Entry; + +#[cfg(not(feature = "hashbrown"))] +use std::collections::hash_map::Entry; + /// A handle that may be used to modify the eventually consistent map. /// /// Note that any changes made to the map will not be made visible to readers until `refresh()` is @@ -117,7 +123,13 @@ where // destructors of any of the values that are in our map, as they'll all be called when the // last read handle goes out of scope. for (_, mut vs) in self.w_handle.as_mut().unwrap().data.drain() { - for v in vs.drain(..) { + #[cfg(not(feature = "smallvec"))] + let drain = vs.drain(..); + + #[cfg(feature = "smallvec")] + let drain = vs.drain(); + + for v in drain { mem::forget(v); } } @@ -181,7 +193,9 @@ where // only block on pre-existing readers, and they are never waiting to push onto epochs // unless they have finished reading. let epochs = Arc::clone(&self.w_handle.as_ref().unwrap().epochs); + let mut epochs = epochs.lock().unwrap(); + self.wait(&mut epochs); { @@ -260,7 +274,8 @@ where let w_handle = Box::into_raw(w_handle); // swap in our w_handle, and get r_handle in return - let r_handle = self.r_handle + let r_handle = self + .r_handle .inner .swap(w_handle, atomic::Ordering::Release); let r_handle = unsafe { Box::from_raw(r_handle) }; @@ -341,14 +356,17 @@ where self.refresh(); // next, grab the read handle and set it to NULL - let r_handle = self.r_handle + let r_handle = self + .r_handle .inner .swap(ptr::null_mut(), atomic::Ordering::Release); let r_handle = unsafe { Box::from_raw(r_handle) }; // now, wait for all readers to depart let epochs = Arc::clone(&self.w_handle.as_ref().unwrap().epochs); + let mut epochs = epochs.lock().unwrap(); + self.wait(&mut epochs); // ensure that the subsequent epoch reads aren't re-ordered to before the swap @@ -458,35 +476,59 @@ where fn apply_first(inner: &mut Inner, op: &mut Operation) { match *op { Operation::Replace(ref key, ref mut value) => { - let vs = inner.data.entry(key.clone()).or_insert_with(Vec::new); - // don't run destructors yet -- still in use by other map - for v in vs.drain(..) { - mem::forget(v); + let vs = inner.data.entry(key.clone()).or_insert_with(Values::new); + + { + #[cfg(not(feature = "smallvec"))] + let drain = vs.drain(..); + + #[cfg(feature = "smallvec")] + let drain = vs.drain(); + + // don't run destructors yet -- still in use by other map + for v in drain { + mem::forget(v); + } } + vs.push(unsafe { value.shallow_copy() }); } Operation::Clear(ref key) => { - // don't run destructors yet -- still in use by other map - for v in inner - .data - .entry(key.clone()) - .or_insert_with(Vec::new) - .drain(..) - { - mem::forget(v); + match inner.data.entry(key.clone()) { + Entry::Occupied(mut occupied) => { + #[cfg(not(feature = "smallvec"))] + let drain = occupied.get_mut().drain(..); + + #[cfg(feature = "smallvec")] + let drain = occupied.get_mut().drain(); + + // don't run destructors yet -- still in use by other map + for v in drain { + mem::forget(v); + } + } + Entry::Vacant(vacant) => { + vacant.insert(Values::new()); + } } } Operation::Add(ref key, ref mut value) => { inner .data .entry(key.clone()) - .or_insert_with(Vec::new) + .or_insert_with(Values::new) .push(unsafe { value.shallow_copy() }); } Operation::Empty(ref key) => { if let Some(mut vs) = inner.data.remove(key) { + #[cfg(not(feature = "smallvec"))] + let drain = vs.drain(..); + + #[cfg(feature = "smallvec")] + let drain = vs.drain(); + // don't run destructors yet -- still in use by other map - for v in vs.drain(..) { + for v in drain { mem::forget(v); } } @@ -507,16 +549,20 @@ where fn apply_second(inner: &mut Inner, op: Operation) { match op { Operation::Replace(key, value) => { - let v = inner.data.entry(key).or_insert_with(Vec::new); + let v = inner.data.entry(key).or_insert_with(Values::new); v.clear(); v.push(value); } Operation::Clear(key) => { - let v = inner.data.entry(key).or_insert_with(Vec::new); + let v = inner.data.entry(key).or_insert_with(Values::new); v.clear(); } Operation::Add(key, value) => { - inner.data.entry(key).or_insert_with(Vec::new).push(value); + inner + .data + .entry(key) + .or_insert_with(Values::new) + .push(value); } Operation::Empty(key) => { inner.data.remove(&key);