use super::*;
const CHURN: core::ops::Range<u64> = 1 << 32..(1 << 32) + 80;
fn churn<M: Map<u64, u64>>(map: &Arc<M>, stop: &Arc<AtomicBool>) -> thread::JoinHandle<()> {
let (m, s) = (Arc::clone(map), Arc::clone(stop));
thread::spawn(move || {
let mut n = 0u64;
while !s.load(Ordering::Relaxed) {
for k in CHURN {
m.insert(k, n);
}
for k in CHURN {
m.remove(&k);
}
n += 1;
}
})
}
mod compare_and_swap_counts_every_winner {
use super::*;
fn run<M: Map<u64, u64>>(calls: u64) {
let _serial = serial();
const THREADS: usize = 8;
const KEYS: u64 = 16;
for round in 0..scaled(4) {
let map = Arc::new(M::with_capacity(64));
for k in 0..KEYS {
map.insert(k, 0);
}
let stop = Arc::new(AtomicBool::new(false));
let churner = churn(&map, &stop);
let m = Arc::clone(&map);
let wins: Vec<Vec<u64>> = together(THREADS, move |t| {
let mut rng = Rng::new((round * THREADS + t) as u64 + 11);
let mut wins = vec![0u64; KEYS as usize];
let mut seen = vec![0u64; KEYS as usize];
for _ in 0..calls {
let k = rng.below(KEYS) as usize;
let expected = seen[k];
match m.compare_and_swap(k as u64, &expected, expected + 1) {
Ok(old) => {
assert_eq!(old, expected, "{} round {round} key {k}", M::NAME);
wins[k] += 1;
seen[k] = expected + 1;
}
Err(Some(current)) => {
assert_ne!(current, expected, "{} round {round} key {k}", M::NAME);
seen[k] = current;
}
Err(None) => panic!("{} round {round}: key {k} vanished", M::NAME),
}
}
wins
});
stop.store(true, Ordering::Relaxed);
churner.join().expect("churn");
for k in 0..KEYS as usize {
let total: u64 = wins.iter().map(|w| w[k]).sum();
assert_eq!(
map.get(&(k as u64)),
Some(total),
"{} round {round} key {k}",
M::NAME
);
}
}
}
#[test]
fn hashmap() {
run::<HashMap<u64, u64, Fold>>(4_000);
}
#[test]
fn hashmap_one_chain() {
run::<HashMap<u64, u64, Constant>>(400);
}
#[test]
fn hopscotch() {
run::<HopscotchMap<u64, u64, Fold>>(4_000);
}
#[test]
fn hopscotch_clustered() {
run::<HopscotchMap<u64, u64, Clustered>>(4_000);
}
}
mod compute_counters_are_exact {
use super::*;
fn run<M: Map<u64, u64>>(calls: u64) {
let _serial = serial();
const THREADS: usize = 8;
const KEYS: u64 = 16;
for round in 0..scaled(4) {
let map = Arc::new(M::with_capacity(64));
let stop = Arc::new(AtomicBool::new(false));
let churner = churn(&map, &stop);
let m = Arc::clone(&map);
let counts: Vec<Vec<u64>> = together(THREADS, move |t| {
let mut rng = Rng::new((round * THREADS + t) as u64 + 23);
let mut counts = vec![0u64; KEYS as usize];
for _ in 0..calls {
let k = rng.below(KEYS);
let after = m.compute(k, |v| Some(v.map_or(1, |v| v + 1)));
assert!(after.is_some_and(|v| v >= 1), "{} round {round}", M::NAME);
counts[k as usize] += 1;
}
counts
});
stop.store(true, Ordering::Relaxed);
churner.join().expect("churn");
for k in 0..KEYS as usize {
let total: u64 = counts.iter().map(|c| c[k]).sum();
let want = (total > 0).then_some(total);
assert_eq!(
map.get(&(k as u64)),
want,
"{} round {round} key {k}",
M::NAME
);
}
}
}
#[test]
fn hashmap() {
run::<HashMap<u64, u64, Fold>>(4_000);
}
#[test]
fn hashmap_one_chain() {
run::<HashMap<u64, u64, Constant>>(400);
}
#[test]
fn hopscotch() {
run::<HopscotchMap<u64, u64, Fold>>(4_000);
}
#[test]
fn hopscotch_clustered() {
run::<HopscotchMap<u64, u64, Clustered>>(4_000);
}
}
mod conditional_races_have_one_winner {
use super::*;
fn the_winner<T>(answers: &[Vec<T>], k: usize, won: impl Fn(&T) -> bool, what: &str) -> usize {
let winners: Vec<usize> = (0..answers.len())
.filter(|&t| won(&answers[t][k]))
.collect();
assert_eq!(winners.len(), 1, "{what} key {k}: winners {winners:?}");
winners[0]
}
fn run<M: Map<u64, u64>>() {
let _serial = serial();
const THREADS: usize = 8;
const KEYS: u64 = 256;
for round in 0..scaled(10) {
let what = format!("{} round {round}", M::NAME);
let map = Arc::new(M::with_capacity(64));
for k in 0..KEYS {
map.insert(k, 0);
}
let stop = Arc::new(AtomicBool::new(false));
let churner = churn(&map, &stop);
let m = Arc::clone(&map);
let removed: Vec<Vec<Option<u64>>> = together(THREADS, move |_| {
(0..KEYS).map(|k| m.compare_and_remove(&k, &0)).collect()
});
for key in 0..KEYS {
let k = key as usize;
let winner = the_winner(&removed, k, Option::is_some, &what);
assert_eq!(removed[winner][k], Some(0), "{what} key {k}");
assert_eq!(map.get(&key), None, "{what} key {k}");
map.insert(key, 1);
}
let m = Arc::clone(&map);
let replaced: Vec<Vec<Result<u64, Option<u64>>>> = together(THREADS, move |t| {
let own = 100 + t as u64;
(0..KEYS)
.map(|k| m.replace_if(k, own, |v| *v == 1))
.collect()
});
let mut winner_of = vec![0u64; KEYS as usize];
for key in 0..KEYS {
let k = key as usize;
let winner = the_winner(&replaced, k, Result::is_ok, &what);
let own = 100 + winner as u64;
assert_eq!(replaced[winner][k], Ok(1), "{what} key {k}");
for (t, answer) in replaced.iter().enumerate() {
if t != winner {
assert_eq!(answer[k], Err(Some(own)), "{what} key {k} thread {t}");
}
}
assert_eq!(map.get(&key), Some(own), "{what} key {k}");
winner_of[k] = own;
}
let m = Arc::clone(&map);
let taken: Vec<Vec<Option<u64>>> = together(THREADS, move |_| {
(0..KEYS).map(|k| m.remove_if(&k, |v| *v >= 100)).collect()
});
stop.store(true, Ordering::Relaxed);
churner.join().expect("churn");
for key in 0..KEYS {
let k = key as usize;
let winner = the_winner(&taken, k, Option::is_some, &what);
assert_eq!(taken[winner][k], Some(winner_of[k]), "{what} key {k}");
assert!(!map.contains_key(&key), "{what} key {k}");
}
}
}
#[test]
fn hashmap() {
run::<HashMap<u64, u64, Fold>>();
}
#[test]
fn hashmap_grouped() {
run::<HashMap<u64, u64, Grouped>>();
}
#[test]
fn hopscotch() {
run::<HopscotchMap<u64, u64, Fold>>();
}
#[test]
fn hopscotch_clustered() {
run::<HopscotchMap<u64, u64, Clustered>>();
}
}
#[test]
fn hopscotch_conditional_writes_find_a_key_being_moved() {
let _serial = serial();
for round in 0..scaled(20) {
let map =
Arc::new(HopscotchMap::<u64, u64, Identity>::with_capacity_and_hasher(64, Identity));
for k in 0..=32 {
map.insert(k, k);
}
let stop = Arc::new(AtomicBool::new(false));
let (m, s) = (Arc::clone(&map), Arc::clone(&stop));
let mover = thread::spawn(move || {
for filler in 66..96u64 {
if m.capacity() != 64 {
break;
}
m.insert(64, 64);
m.remove(&64);
m.insert(filler, filler);
}
s.store(true, Ordering::Relaxed);
});
let (m, s) = (Arc::clone(&map), Arc::clone(&stop));
let checker = thread::spawn(move || {
let mut rng = Rng::new(round as u64 + 5);
while !s.load(Ordering::Relaxed) {
let k = 2 + rng.below(31);
match rng.below(4) {
0 => assert_eq!(
m.compare_and_swap(k, &k, k),
Ok(k),
"round {round}: key {k}"
),
1 => assert_eq!(
m.replace_if(k, k, |v| *v == k),
Ok(k),
"round {round}: key {k}"
),
2 => assert_eq!(
m.compute(k, |v| v.copied()),
Some(k),
"round {round}: key {k}"
),
_ => {
assert_eq!(
m.remove_if(&k, |v| *v == k),
Some(k),
"round {round}: key {k}"
);
assert_eq!(
m.compute(k, |v| {
assert_eq!(v, None, "round {round}: key {k} came back");
Some(k)
}),
Some(k)
);
}
}
}
});
mover.join().expect("mover");
checker.join().expect("checker");
for k in 0..=32 {
assert_eq!(map.get(&k), Some(k), "round {round}: key {k}");
}
}
}