Skip to content

Add optional features that can potentially improve performance #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
9 changes: 8 additions & 1 deletion benchmark/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
15 changes: 8 additions & 7 deletions benchmark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -87,7 +88,7 @@ fn main() {
// first, benchmark Arc<RwLock<HashMap>>
if matches.is_present("compare") {
let map: HashMap<u64, u64> = 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(|_| {
Expand Down Expand Up @@ -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(|_| {
Expand Down Expand Up @@ -206,19 +207,19 @@ impl Backend for sync::Arc<CHashMap<u64, u64>> {
}
}

impl Backend for sync::Arc<sync::RwLock<HashMap<u64, u64>>> {
impl Backend for sync::Arc<parking_lot::RwLock<HashMap<u64, u64>>> {
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<u64, u64>),
Write(sync::Arc<sync::Mutex<(evmap::WriteHandle<u64, u64>, usize, usize)>>),
Write(sync::Arc<parking_lot::Mutex<(evmap::WriteHandle<u64, u64>, usize, usize)>>),
}

impl Backend for EvHandle {
Expand All @@ -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 {
Expand Down
15 changes: 13 additions & 2 deletions src/inner.rs
Original file line number Diff line number Diff line change
@@ -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<T> = Vec<T>;

#[cfg(feature = "smallvec")]
pub(crate) type Values<T> = smallvec::SmallVec<[T; 1]>;

pub(crate) struct Inner<K, V, M, S>
where
K: Eq + Hash,
S: BuildHasher,
{
pub(crate) data: HashMap<K, Vec<V>, S>,
pub(crate) data: HashMap<K, Values<V>, S>,
pub(crate) epochs: Arc<Mutex<Vec<Arc<atomic::AtomicUsize>>>>,
pub(crate) meta: M,
ready: bool,
Expand Down
34 changes: 34 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<K, V>() -> (
ReadHandle<K, V, (), RandomState>,
Expand All @@ -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<K, V, M>(
meta: M,
Expand All @@ -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<K, V, M, S>(
meta: M,
hasher: S,
) -> (ReadHandle<K, V, M, S>, WriteHandle<K, V, M, S>)
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]
Expand Down
11 changes: 8 additions & 3 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()))
}
}
88 changes: 67 additions & 21 deletions src/write.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{Operation, ShallowCopy};
use inner::Inner;
use inner::{Inner, Values};
use read::ReadHandle;

use std::collections::hash_map::RandomState;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);

{
Expand Down Expand Up @@ -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) };
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -458,35 +476,59 @@ where
fn apply_first(inner: &mut Inner<K, V, M, S>, op: &mut Operation<K, V>) {
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);
}
}
Expand All @@ -507,16 +549,20 @@ where
fn apply_second(inner: &mut Inner<K, V, M, S>, op: Operation<K, V>) {
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);
Expand Down