use ahash::RandomState;
use bytemuck::{Pod, Zeroable};
use rostl_primitives::{
cmov_body, cxchg_body, impl_cmov_for_generic_pod,
ooption::OOption,
traits::{Cmov, _Cmovbase},
};
use rostl_sort::{
bitonic::{bitonic_payload_sort, bitonic_sort},
compaction::{compact, compact_payload, distribute_payload},
};
use crate::map::{OHash, UnsortedMap};
use kanal::{bounded, unbounded, Receiver, Sender};
use std::{
io,
sync::{Arc, Barrier},
thread,
};
const P: usize = 15;
enum Reply<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord,
V: Cmov + Pod + Default + std::fmt::Debug,
BatchBlock<K, V>: Ord + Send,
{
Blocks { pid: usize, blocks: Vec<BatchBlock<K, V>> },
Unit(()),
}
enum Replyv2<V>
where
V: Cmov + Pod + Default + std::fmt::Debug,
{
Blocks { pid: usize, offset: usize, values: Vec<OOption<V>> },
Unit(()),
}
enum Cmd<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
BatchBlock<K, V>: Ord + Send,
{
Get {
blocks: Vec<BatchBlock<K, V>>,
ret_tx: Sender<Reply<K, V>>,
},
Insert {
blocks: Vec<BatchBlock<K, V>>,
ret_tx: Sender<Reply<K, V>>,
},
Getv2 {
offset: usize,
blocks: Vec<K>,
},
Insertv2 {
blocks: Vec<KeyWithPartValue<K, V>>,
},
Shutdown,
}
#[derive(Debug)]
struct Worker<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
BatchBlock<K, V>: Ord + Send,
{
tx: Sender<Cmd<K, V>>,
join_handle: Option<thread::JoinHandle<()>>,
}
#[allow(unused)]
fn pin_current_thread_to(cpu: usize) -> io::Result<()> {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut set);
libc::CPU_SET(cpu, &mut set);
let ret = libc::pthread_setaffinity_np(
libc::pthread_self(),
std::mem::size_of::<libc::cpu_set_t>(),
&raw const set,
);
if ret != 0 {
return Err(io::Error::from_raw_os_error(ret));
}
}
Ok(())
}
fn set_current_thread_rt(priority: i32) -> io::Result<()> {
unsafe {
let ret = libc::setpriority(libc::PRIO_PROCESS, 0, priority);
if ret != 0 {
eprintln!("setpriority failed: {}", std::io::Error::last_os_error());
}
}
Ok(())
}
impl<K, V> Worker<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Send,
V: Cmov + Pod + Default + std::fmt::Debug + Send + Eq,
BatchBlock<K, V>: Ord + Send,
{
fn new(
n: usize,
pid: usize,
startup_barrier: Arc<Barrier>,
reply_channel: Sender<Replyv2<V>>,
) -> Self {
let (tx, rx): (Sender<Cmd<_, _>>, Receiver<_>) = unbounded();
let handler = thread::Builder::new()
.name(format!("partition-{pid}"))
.spawn(move || {
set_current_thread_rt(0).expect("failed to set thread to real-time priority");
startup_barrier.wait();
let mut map = UnsortedMap::<K, V>::new(n);
loop {
let cmd = match rx.recv() {
Ok(cmd) => cmd,
Err(_) => {
panic!("worker thread command channel disconnected unexpectedly");
}
};
match cmd {
Cmd::Get { mut blocks, ret_tx } => {
for blk in &mut blocks {
blk.v = OOption::new(Default::default(), true);
blk.v.is_some = map.get(blk.k, &mut blk.v.value);
}
let _ = ret_tx.send(Reply::Blocks { pid, blocks }); }
Cmd::Insert { blocks, ret_tx } => {
for blk in &blocks {
map.insert_cond(blk.k, blk.v.value, blk.v.is_some);
}
let _ = ret_tx.send(Reply::Unit(()));
}
Cmd::Getv2 { offset, blocks } => {
let mut values = vec![OOption::<V>::default(); blocks.len()];
for (i, k) in blocks.iter().enumerate() {
values[i].is_some = map.get(*k, &mut values[i].value);
}
let _ = reply_channel.send(Replyv2::Blocks { pid, offset, values });
}
Cmd::Insertv2 { blocks } => {
for blk in &blocks {
let real = blk.partition == pid;
map.insert_cond(blk.key, blk.value, real);
}
let _ = reply_channel.send(Replyv2::Unit(()));
}
Cmd::Shutdown => break,
}
}
})
.expect("failed to spawn worker thread");
Self { tx, join_handle: Some(handler) }
}
}
impl<K, V> Drop for Worker<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
BatchBlock<K, V>: Ord + Send,
{
fn drop(&mut self) {
let _ = self.tx.send(Cmd::Shutdown);
match self.join_handle.take() {
Some(handle) => {
let _ = handle.join();
}
None => {
panic!("Exception while dropping worker thread, handler was already taken");
}
}
}
}
#[derive(Debug)]
pub struct ShardedMap<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Send + Ord,
V: Cmov + Pod + Default + std::fmt::Debug + Send + Eq,
BatchBlock<K, V>: Ord + Send,
{
size: usize,
capacity: usize,
workers: [Worker<K, V>; P],
random_state: RandomState,
response_channel: Receiver<Replyv2<V>>,
}
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, Zeroable, PartialEq, Eq, PartialOrd, Ord)]
pub struct BatchBlock<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord,
V: Cmov + Pod + Default + std::fmt::Debug,
{
index: usize,
k: K,
v: OOption<V>,
}
unsafe impl<K, V> Pod for BatchBlock<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord,
V: Cmov + Pod + Default + std::fmt::Debug,
{
}
impl_cmov_for_generic_pod!(BatchBlock<K, V>; where K: OHash + Pod + Default + std::fmt::Debug + Ord, V: Cmov + Pod + Default + std::fmt::Debug);
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, PartialEq, Eq)]
struct KeyWithPart<K>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
{
partition: usize,
key: K,
}
unsafe impl<K> Pod for KeyWithPart<K> where K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized {}
impl_cmov_for_generic_pod!(KeyWithPart<K>; where K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized);
impl<K> KeyWithPart<K>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
{
fn cmp_ct(&self, other: &Self) -> std::cmp::Ordering {
let part = self.partition.cmp(&other.partition) as i8;
let key = self.key.cmp(&other.key) as i8;
let mut res = part;
res.cmov(&key, part == 0);
res.cmp(&0)
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl<K> PartialOrd for KeyWithPart<K>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp_ct(other))
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl<K> Ord for KeyWithPart<K>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.cmp_ct(other)
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, PartialEq, Eq)]
struct KeyWithPartValue<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
{
partition: usize,
key: K,
value: V,
}
unsafe impl<K, V> Pod for KeyWithPartValue<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
{
}
impl_cmov_for_generic_pod!(KeyWithPartValue<K, V>; where K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized, V: Cmov + Pod + Default + std::fmt::Debug +Eq);
impl<K, V> KeyWithPartValue<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
{
fn cmp_ct(&self, other: &Self) -> std::cmp::Ordering {
let part = self.partition.cmp(&other.partition) as i8;
let key = self.key.cmp(&other.key) as i8;
let mut res = part;
res.cmov(&key, part == 0);
res.cmp(&0)
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl<K, V> PartialOrd for KeyWithPartValue<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp_ct(other))
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl<K, V> Ord for KeyWithPartValue<K, V>
where
K: OHash + Pod + Default + std::fmt::Debug + Ord + Sized,
V: Cmov + Pod + Default + std::fmt::Debug + Eq,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.cmp_ct(other)
}
}
impl<K, V> ShardedMap<K, V>
where
K: OHash + Default + std::fmt::Debug + Send + Ord + Pod + Sized,
V: Cmov + Pod + Default + std::fmt::Debug + Send + Eq,
BatchBlock<K, V>: Ord + Send,
{
pub fn new(capacity: usize) -> Self {
let per_part = capacity.div_ceil(P);
let startup = Arc::new(Barrier::new(P + 1));
let (reply_tx, response_channel) = unbounded::<Replyv2<V>>();
let workers =
std::array::from_fn(|i| Worker::new(per_part, i, startup.clone(), reply_tx.clone()));
startup.wait();
Self {
size: 0,
capacity: per_part * P,
workers,
random_state: RandomState::new(),
response_channel,
}
}
#[inline(always)]
fn get_partition(&self, key: &K) -> usize {
(self.random_state.hash_one(key) % P as u64) as usize
}
pub const fn compute_safe_batch_size(&self, n: usize) -> usize {
let a = n.div_ceil(P) + (n * (P.ilog2() as usize + 1)).div_ceil(P).isqrt() + 20; if a < n {
a
} else {
n
}
}
pub fn get_batch_distinct(&mut self, keys: &[K], b: usize) -> Vec<OOption<V>> {
let n: usize = keys.len();
assert!(b <= n, "batch size b must be <= number of keys");
let mut per_p: [Box<Vec<BatchBlock<K, V>>>; P] =
std::array::from_fn(|_| Box::new(vec![BatchBlock::default(); n]));
const INVALID_ID: usize = usize::MAX;
for (i, k) in keys.iter().enumerate() {
let target_p = self.get_partition(k);
for (p, partition) in per_p.iter_mut().enumerate() {
partition[i].k = *k;
partition[i].index = i;
partition[i].index.cmov(&INVALID_ID, target_p != p);
}
}
for partition in &mut per_p {
let cnt = compact(partition, |x: &BatchBlock<K, V>| x.index == INVALID_ID);
assert!(cnt <= b);
}
let (done_tx, done_rx) = bounded::<Reply<K, V>>(P);
for (p, partition) in per_p.iter_mut().enumerate() {
let blocks: Vec<BatchBlock<K, V>> = partition[..b].to_vec();
self.workers[p].tx.send(Cmd::Get { blocks, ret_tx: done_tx.clone() }).unwrap();
}
let mut merged: Vec<BatchBlock<K, V>> = vec![BatchBlock::default(); P * b];
for _ in 0..P {
match done_rx.recv().unwrap() {
Reply::Blocks { pid, blocks } => {
for i in 0..b {
merged[pid * b + i] = blocks[i];
}
}
_ => panic!("unexpected reply from worker thread (probably early termination?)"),
}
}
bitonic_sort(&mut merged);
let mut ret: Vec<OOption<V>> = vec![OOption::default(); n];
for i in 0..n {
ret[i] = merged[i].v;
}
ret
}
pub fn get_batch(&mut self, keys: &[K], b: usize) -> Vec<OOption<V>> {
let n: usize = keys.len();
assert!(n > 0, "get_batch requires at least one key");
assert!(b <= n, "batch size b must be <= number of keys");
let bp = b * P;
assert!(b >= n.div_ceil(P), "batch size b must be >= n/P to avoid overflow");
const SUBTASK_SIZE: usize = 32;
let mut keyinfo = vec![KeyWithPart { partition: P, key: K::default() }; bp];
for i in 0..n {
keyinfo[i].key = keys[i];
keyinfo[i].partition = self.get_partition(&keys[i]);
}
let mut index_map_1 = (0..n).collect::<Vec<usize>>();
bitonic_payload_sort::<KeyWithPart<K>, [KeyWithPart<K>], usize>(
&mut keyinfo[..n],
&mut index_map_1,
);
let mut par_load = [0; P];
let mut prefix_sum_1 = vec![0; n + 1];
prefix_sum_1[1] = 1;
for (j, load) in par_load.iter_mut().enumerate() {
let cond = keyinfo[0].partition == j;
load.cmov(&1, cond);
}
for i in 1..n {
let new_key = keyinfo[i].key != keyinfo[i - 1].key;
prefix_sum_1[i + 1] = prefix_sum_1[i];
let alt = prefix_sum_1[i] + 1;
prefix_sum_1[i + 1].cmov(&alt, new_key);
for (j, load) in par_load.iter_mut().enumerate() {
let cond = keyinfo[i].partition == j;
let alt = *load + 1;
load.cmov(&alt, cond & new_key);
}
}
for (j, load) in par_load.iter().enumerate() {
assert!(*load <= b, "Too many distinct keys in partition {j}: {}, increase b", *load);
}
compact_payload(&mut keyinfo[..n], &prefix_sum_1);
let mut par_load_ps = [0; P + 1];
for j in 0..P {
par_load_ps[j + 1] = par_load_ps[j] + par_load[j];
}
let mut prefix_sum_2 = vec![0; bp + 1];
for j in 0..P {
for i in 0..b {
let mut rank_in_part = i + 1;
rank_in_part.cmov(&par_load[j], rank_in_part > par_load[j]);
prefix_sum_2[j * b + i + 1] = par_load_ps[j] + rank_in_part;
}
}
distribute_payload(&mut keyinfo, &prefix_sum_2);
let mut sent_count = 0;
for j in 0..P {
for k in 0..b.div_ceil(SUBTASK_SIZE) {
let offset = k * SUBTASK_SIZE;
let low = j * b + offset;
let high = (low + SUBTASK_SIZE).min((j + 1) * b);
let blocks: Vec<K> = keyinfo[low..high].iter().map(|x| x.key).collect();
self.workers[j].tx.send(Cmd::Getv2 { offset, blocks }).unwrap();
sent_count += 1;
}
}
let mut res = vec![OOption::<V>::default(); bp];
for _ in 0..sent_count {
match self.response_channel.recv().unwrap() {
Replyv2::Blocks { pid, offset, values } => {
for (val, res) in
values.iter().zip(res.iter_mut().skip(pid * b + offset)).take(SUBTASK_SIZE)
{
*res = *val;
}
}
_ => panic!("unexpected reply from worker thread (probably early termination?)"),
}
}
compact_payload(&mut res, &prefix_sum_2);
distribute_payload(&mut res[..n], &prefix_sum_1);
for i in 1..n {
let cond = prefix_sum_1[i] == prefix_sum_1[i - 1];
let copy = res[i - 1];
res[i].cmov(©, cond);
}
res.truncate(n);
bitonic_payload_sort(&mut index_map_1[..n], &mut res);
res
}
#[deprecated(
note = "This function is unsafe because it can potentially leak information about keys to partition mapping. Use get_batch_distinct instead."
)]
pub unsafe fn get_batch_leaky(&mut self, keys: &[K]) -> Vec<OOption<V>> {
let n: usize = keys.len();
let mut b = 0;
let mut per_p: [Box<Vec<BatchBlock<K, V>>>; P] =
std::array::from_fn(|_| Box::new(vec![BatchBlock::default(); n]));
const INVALID_ID: usize = usize::MAX;
for (i, k) in keys.iter().enumerate() {
let target_p = self.get_partition(k);
for (p, partition) in per_p.iter_mut().enumerate() {
partition[i].k = *k;
partition[i].index = i;
partition[i].index.cmov(&INVALID_ID, target_p != p);
}
}
for partition in &mut per_p {
let cnt = compact(partition, |x: &BatchBlock<K, V>| x.index == INVALID_ID);
b = b.max(cnt);
}
let (done_tx, done_rx) = bounded::<Reply<K, V>>(P);
for (p, partition) in per_p.iter_mut().enumerate() {
let blocks: Vec<BatchBlock<K, V>> = partition[..b].to_vec();
self.workers[p].tx.send(Cmd::Get { blocks, ret_tx: done_tx.clone() }).unwrap();
}
let mut merged: Vec<BatchBlock<K, V>> = vec![BatchBlock::default(); P * b];
for _ in 0..P {
match done_rx.recv().unwrap() {
Reply::Blocks { pid, blocks } => {
for i in 0..b {
merged[pid * b + i] = blocks[i];
}
}
_ => panic!("unexpected reply from worker thread (probably early termination?)"),
}
}
bitonic_sort(&mut merged);
let mut ret: Vec<OOption<V>> = vec![OOption::default(); n];
for i in 0..n {
ret[i] = merged[i].v;
}
ret
}
pub fn insert_batch_distinct(&mut self, keys: &[K], values: &[V], b: usize) {
let n = keys.len();
assert!(n == values.len(), "Invalid input: keys and values must have the same length");
assert!(self.size + n <= self.capacity, "Map is full, cannot insert more elements.");
assert!(b <= n, "batch size b must be <= number of keys");
let mut per_p: [Box<Vec<BatchBlock<K, V>>>; P] =
std::array::from_fn(|_| Box::new(vec![BatchBlock::default(); n]));
const INVALID_ID: usize = usize::MAX;
for (i, k) in keys.iter().enumerate() {
let target_p = self.get_partition(k);
for (p, partition) in per_p.iter_mut().enumerate() {
partition[i].k = *k;
partition[i].v = OOption::new(values[i], true);
partition[i].index = i;
partition[i].index.cmov(&INVALID_ID, target_p != p);
}
}
for partition in &mut per_p {
let cnt = compact(partition, |x| x.index == INVALID_ID);
assert!(cnt <= b);
}
let (done_tx, done_rx) = bounded::<Reply<K, V>>(P);
for (p, partition) in per_p.iter_mut().enumerate() {
let blocks: Vec<BatchBlock<K, V>> = partition[..b].to_vec();
self.workers[p].tx.send(Cmd::Insert { blocks, ret_tx: done_tx.clone() }).unwrap();
}
for _i in 0..P {
match done_rx.recv().unwrap() {
Reply::Unit(()) => {}
_ => {
panic!("unexpected reply from worker thread (probably early termination?)");
}
}
}
self.size += n;
}
pub fn insert_batch(&mut self, keys: &[K], values: &[V], b: usize) {
let n: usize = keys.len();
assert!(n > 0, "get_batch requires at least one key");
assert!(b <= n, "batch size b must be <= number of keys");
let bp = b * P;
assert!(b >= n.div_ceil(P), "batch size b must be >= n/P to avoid overflow");
let mut keyinfo =
vec![KeyWithPartValue { partition: P, key: K::default(), value: V::default() }; bp];
for i in 0..n {
keyinfo[i].key = keys[i];
keyinfo[i].value = values[i];
keyinfo[i].partition = self.get_partition(&keys[i]);
}
let mut index_map_1 = (0..n).collect::<Vec<usize>>();
bitonic_payload_sort::<KeyWithPartValue<K, V>, [KeyWithPartValue<K, V>], usize>(
&mut keyinfo[..n],
&mut index_map_1,
);
let mut par_load = [0; P];
let mut prefix_sum_1 = vec![0; n + 1];
prefix_sum_1[1] = 1;
for (j, load) in par_load.iter_mut().enumerate() {
let cond = keyinfo[0].partition == j;
load.cmov(&1, cond);
}
for i in 1..n {
let new_key = keyinfo[i].key != keyinfo[i - 1].key;
prefix_sum_1[i + 1] = prefix_sum_1[i];
let alt = prefix_sum_1[i] + 1;
prefix_sum_1[i + 1].cmov(&alt, new_key);
keyinfo[i].partition.cmov(&P, !new_key);
for (j, load) in par_load.iter_mut().enumerate() {
let cond = keyinfo[i].partition == j;
let alt = *load + 1;
load.cmov(&alt, cond & new_key);
}
}
for (j, load) in par_load.iter().enumerate() {
assert!(*load <= b, "Too many distinct keys in partition {j}: {}, increase b", *load);
}
compact_payload(&mut keyinfo[..n], &prefix_sum_1);
let mut par_load_ps = [0; P + 1];
for j in 0..P {
par_load_ps[j + 1] = par_load_ps[j] + par_load[j];
}
self.size += par_load_ps[P];
let mut prefix_sum_2 = vec![0; bp + 1];
for j in 0..P {
for i in 0..b {
let mut rank_in_part = i + 1;
rank_in_part.cmov(&par_load[j], rank_in_part > par_load[j]);
prefix_sum_2[j * b + i + 1] = par_load_ps[j] + rank_in_part;
}
}
distribute_payload(&mut keyinfo, &prefix_sum_2);
const SUBTASK_SIZE: usize = 32;
let mut sent_count = 0;
for j in 0..P {
for k in 0..b.div_ceil(SUBTASK_SIZE) {
let low = j * b + k * SUBTASK_SIZE;
let high = (low + SUBTASK_SIZE).min((j + 1) * b);
let blocks: Vec<KeyWithPartValue<K, V>> = keyinfo[low..high].to_vec();
self.workers[j].tx.send(Cmd::Insertv2 { blocks }).unwrap();
sent_count += 1;
}
}
for _ in 0..sent_count {
match self.response_channel.recv().unwrap() {
Replyv2::Unit(()) => {}
_ => panic!("unexpected reply from worker thread (probably early termination?)"),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const N: usize = 4;
#[test]
fn test_map_sendness() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<ShardedMap<u64, u64>>();
assert_sync::<ShardedMap<u64, u64>>();
}
#[test]
fn new_map_rounds_capacity_and_starts_empty() {
let requested = 100;
let map: ShardedMap<u64, u64> = ShardedMap::new(requested);
let per_part = requested.div_ceil(P);
assert_eq!(map.capacity, per_part * P); assert_eq!(map.size, 0);
}
#[test]
fn insert_batch_then_get_batch_returns_expected_values() {
let mut map: ShardedMap<u64, u64> = ShardedMap::new(32);
let keys: [u64; N] = [1, 2, 3, 4];
let values: [u64; N] = [10, 20, 30, 40];
map.insert_batch_distinct(&keys, &values, N);
let results = map.get_batch_distinct(&keys, N);
for i in 0..N {
assert!(results[i].is_some(), "key {} missing", keys[i]);
assert_eq!(results[i].unwrap(), values[i]);
}
#[allow(deprecated)]
{
let results = unsafe { map.get_batch_leaky(&keys) };
for i in 0..N {
assert!(results[i].is_some(), "key {} missing", keys[i]);
assert_eq!(results[i].unwrap(), values[i]);
}
}
let results = map.get_batch(&keys, N);
for i in 0..N {
assert!(results[i].is_some(), "key {} missing", keys[i]);
assert_eq!(results[i].unwrap(), values[i]);
}
}
#[test]
fn querying_absent_keys_returns_none() {
let mut map: ShardedMap<u64, u64> = ShardedMap::new(16);
let absent: [u64; N] = [100, 200, 300, 400];
let results = map.get_batch_distinct(&absent, N);
for r in &results {
assert!(!r.is_some());
}
#[allow(deprecated)]
{
let results = unsafe { map.get_batch_leaky(&absent) };
for r in &results {
assert!(!r.is_some());
}
}
let absent: [u64; N] = [100, 200, 300, 400];
let results = map.get_batch(&absent, N);
for r in &results {
assert!(!r.is_some());
}
}
#[test]
fn size_updates_after_insert() {
let mut map: ShardedMap<u64, u64> = ShardedMap::new(16);
let keys: [u64; N] = [11, 22, 33, 44];
let values: [u64; N] = [111, 222, 333, 444];
map.insert_batch_distinct(&keys, &values, N);
assert_eq!(map.size, N);
let mut map: ShardedMap<u64, u64> = ShardedMap::new(16);
map.insert_batch(&keys, &values, N);
assert_eq!(map.size, N);
}
#[test]
fn compute_safe_batch_size_works() {
let map: ShardedMap<u64, u64> = ShardedMap::new(16);
assert_eq!(P, 15);
let n = 100;
let b = map.compute_safe_batch_size(n);
assert!(b >= n.div_ceil(P));
assert_eq!(b, 32);
let n = 1000;
let b = map.compute_safe_batch_size(n);
assert!(b >= n.div_ceil(P));
assert_eq!(b, 103);
let n = 4096;
let b = map.compute_safe_batch_size(n);
assert!(b >= n.div_ceil(P));
assert_eq!(b, 327);
let n = 8192;
let b = map.compute_safe_batch_size(n);
assert!(b >= n.div_ceil(P));
assert_eq!(b, 613);
for i in 1..100 {
for j in 1..100 {
let n = i * j * P;
let b = map.compute_safe_batch_size(n);
assert!(b >= n.div_ceil(P));
assert!(b <= n);
}
}
}
}