191 lines
6.3 KiB
Rust
191 lines
6.3 KiB
Rust
// https://github.com/efficient/libcuckoo/tree/master/tests/stress-tests
|
|
|
|
use flurry::*;
|
|
use parking_lot::Mutex;
|
|
use rand::distributions::{Distribution, Uniform};
|
|
use std::sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc,
|
|
};
|
|
use std::thread;
|
|
|
|
/// Number of keys and values to work with.
|
|
const NUM_KEYS: usize = 1 << 12;
|
|
/// Number of threads that should be started.
|
|
const NUM_THREADS: usize = 4;
|
|
/// How long the stress test will run (in milliseconds).
|
|
const TEST_LEN: u64 = 5000;
|
|
|
|
type Key = usize;
|
|
type Value = usize;
|
|
|
|
struct Environment {
|
|
table1: HashMap<Key, Value>,
|
|
table2: HashMap<Key, Value>,
|
|
keys: Vec<Key>,
|
|
vals1: Mutex<Vec<Value>>,
|
|
vals2: Mutex<Vec<Value>>,
|
|
ind_dist: Uniform<usize>,
|
|
val_dist1: Uniform<Value>,
|
|
val_dist2: Uniform<Value>,
|
|
in_table: Mutex<Vec<bool>>,
|
|
in_use: Mutex<Vec<AtomicBool>>,
|
|
finished: AtomicBool,
|
|
}
|
|
|
|
impl Environment {
|
|
pub fn new() -> Self {
|
|
let mut keys = Vec::with_capacity(NUM_KEYS);
|
|
let mut in_use = Vec::with_capacity(NUM_KEYS);
|
|
|
|
for i in 0..NUM_KEYS {
|
|
keys.push(i);
|
|
in_use.push(AtomicBool::new(false));
|
|
}
|
|
|
|
Self {
|
|
table1: HashMap::new(),
|
|
table2: HashMap::new(),
|
|
keys,
|
|
vals1: Mutex::new(vec![0usize; NUM_KEYS]),
|
|
vals2: Mutex::new(vec![0usize; NUM_KEYS]),
|
|
ind_dist: Uniform::from(0..NUM_KEYS - 1),
|
|
val_dist1: Uniform::from(Value::min_value()..Value::max_value()),
|
|
val_dist2: Uniform::from(Value::min_value()..Value::max_value()),
|
|
in_table: Mutex::new(vec![false; NUM_KEYS]),
|
|
in_use: Mutex::new(in_use),
|
|
finished: AtomicBool::new(false),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn stress_insert_thread(env: Arc<Environment>) {
|
|
let mut rng = rand::thread_rng();
|
|
let guard1 = env.table1.guard();
|
|
let guard2 = env.table2.guard();
|
|
|
|
while !env.finished.load(Ordering::SeqCst) {
|
|
let idx = env.ind_dist.sample(&mut rng);
|
|
let in_use = env.in_use.lock();
|
|
if (*in_use)[idx]
|
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
let key = env.keys[idx];
|
|
let val1 = env.val_dist1.sample(&mut rng);
|
|
let val2 = env.val_dist2.sample(&mut rng);
|
|
let res1 = if !env.table1.contains_key(&key, &guard1) {
|
|
env.table1
|
|
.insert(key, val1, &guard1)
|
|
.map_or(true, |_| false)
|
|
} else {
|
|
false
|
|
};
|
|
let res2 = if !env.table2.contains_key(&key, &guard2) {
|
|
env.table2
|
|
.insert(key, val2, &guard2)
|
|
.map_or(true, |_| false)
|
|
} else {
|
|
false
|
|
};
|
|
let mut in_table = env.in_table.lock();
|
|
assert_ne!(res1, (*in_table)[idx]);
|
|
assert_ne!(res2, (*in_table)[idx]);
|
|
if res1 {
|
|
assert_eq!(Some(&val1), env.table1.get(&key, &guard1));
|
|
assert_eq!(Some(&val2), env.table2.get(&key, &guard2));
|
|
let mut vals1 = env.vals1.lock();
|
|
let mut vals2 = env.vals2.lock();
|
|
(*vals1)[idx] = val1;
|
|
(*vals2)[idx] = val2;
|
|
(*in_table)[idx] = true;
|
|
}
|
|
(*in_use)[idx].swap(false, Ordering::SeqCst);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn stress_delete_thread(env: Arc<Environment>) {
|
|
let mut rng = rand::thread_rng();
|
|
let guard1 = env.table1.guard();
|
|
let guard2 = env.table2.guard();
|
|
|
|
while !env.finished.load(Ordering::SeqCst) {
|
|
let idx = env.ind_dist.sample(&mut rng);
|
|
let in_use = env.in_use.lock();
|
|
if (*in_use)[idx]
|
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
let key = env.keys[idx];
|
|
let res1 = env.table1.remove(&key, &guard1).map_or(false, |_| true);
|
|
let res2 = env.table2.remove(&key, &guard2).map_or(false, |_| true);
|
|
let mut in_table = env.in_table.lock();
|
|
assert_eq!(res1, (*in_table)[idx]);
|
|
assert_eq!(res2, (*in_table)[idx]);
|
|
if res1 {
|
|
assert!(env.table1.get(&key, &guard1).is_none());
|
|
assert!(env.table2.get(&key, &guard2).is_none());
|
|
(*in_table)[idx] = false;
|
|
}
|
|
(*in_use)[idx].swap(false, Ordering::SeqCst);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn stress_find_thread(env: Arc<Environment>) {
|
|
let mut rng = rand::thread_rng();
|
|
let guard1 = env.table1.guard();
|
|
let guard2 = env.table2.guard();
|
|
|
|
while !env.finished.load(Ordering::SeqCst) {
|
|
let idx = env.ind_dist.sample(&mut rng);
|
|
let in_use = env.in_use.lock();
|
|
if (*in_use)[idx]
|
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
let key = env.keys[idx];
|
|
let in_table = env.in_table.lock();
|
|
let val1 = (*env.vals1.lock())[idx];
|
|
let val2 = (*env.vals2.lock())[idx];
|
|
|
|
let value = env.table1.get(&key, &guard1);
|
|
if value.is_some() {
|
|
assert_eq!(&val1, value.unwrap());
|
|
assert!((*in_table)[idx]);
|
|
}
|
|
let value = env.table2.get(&key, &guard2);
|
|
if value.is_some() {
|
|
assert_eq!(&val2, value.unwrap());
|
|
assert!((*in_table)[idx]);
|
|
}
|
|
(*in_use)[idx].swap(false, Ordering::SeqCst);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn stress_test() {
|
|
let root = Arc::new(Environment::new());
|
|
let mut threads = Vec::new();
|
|
for _ in 0..NUM_THREADS {
|
|
let env = Arc::clone(&root);
|
|
threads.push(thread::spawn(move || stress_insert_thread(env)));
|
|
let env = Arc::clone(&root);
|
|
threads.push(thread::spawn(move || stress_delete_thread(env)));
|
|
let env = Arc::clone(&root);
|
|
threads.push(thread::spawn(move || stress_find_thread(env)));
|
|
}
|
|
thread::sleep(std::time::Duration::from_millis(TEST_LEN));
|
|
root.finished.swap(true, Ordering::SeqCst);
|
|
for t in threads {
|
|
t.join().expect("failed to join thread");
|
|
}
|
|
let in_table = &*root.in_table.lock();
|
|
let num_filled = in_table.iter().filter(|b| **b).count();
|
|
assert_eq!(num_filled, root.table1.len());
|
|
assert_eq!(num_filled, root.table2.len());
|
|
}
|