use std::collections::HashSet;
use std::hash::{BuildHasherDefault, Hasher};
use std::time::Instant;
#[derive(Default)]
struct U64Hasher(u64);
impl Hasher for U64Hasher {
#[inline(always)]
fn write_u64(&mut self, n: u64) {
self.0 = n.wrapping_mul(0x9E37_79B9_7F4A_7C15);
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, _: &[u8]) {
unreachable!()
}
}
type U64Set = HashSet<u64, BuildHasherDefault<U64Hasher>>;
#[inline(always)]
fn xorshift(s: &mut u64) -> u64 {
*s ^= *s << 13;
*s ^= *s >> 7;
*s ^= *s << 17;
*s
}
#[inline(always)]
#[allow(unused_variables)]
fn prefetch_read<T>(ptr: *const T) {
#[cfg(target_arch = "aarch64")]
unsafe {
std::arch::asm!(
"prfm pldl1keep, [{p}]",
p = in(reg) ptr,
options(nostack, preserves_flags, readonly),
);
}
#[cfg(target_arch = "x86_64")]
unsafe {
std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
}
}
fn populate(n_cells: usize, steady_size: usize, seed_start: u64) -> Vec<U64Set> {
let mut sets: Vec<U64Set> =
(0..n_cells).map(|_| U64Set::with_capacity_and_hasher(64, Default::default())).collect();
let mut seed = seed_start;
for cell in &mut sets {
for _ in 0..steady_size {
cell.insert(xorshift(&mut seed));
}
}
sets
}
fn main() {
const N_CELLS: usize = 4096;
const STEADY_SIZE: usize = 40_000;
const N_OPS: usize = 50_000_000;
let batches = [4usize, 8, 16, 32];
eprintln!("Populating {} cells × {} entries...", N_CELLS, STEADY_SIZE);
let t0 = Instant::now();
let template = populate(N_CELLS, STEADY_SIZE, 0xDEAD_BEEF);
eprintln!(" ({:?})", t0.elapsed());
eprintln!("Generating {} ops...", N_OPS);
let mut ops: Vec<(u32, u64)> = Vec::with_capacity(N_OPS);
let mut seed = 0xABCD_0123_u64;
for _ in 0..N_OPS {
let off = (xorshift(&mut seed) % N_CELLS as u64) as u32;
let sig = xorshift(&mut seed);
ops.push((off, sig));
}
let mut sets = template.clone();
let mut dup_count = 0usize;
let t1 = Instant::now();
for &(off, sig) in &ops {
if !sets[off as usize].insert(sig) {
dup_count += 1;
}
}
let baseline = t1.elapsed();
eprintln!("\nbaseline: {:.3}s ({} dups)", baseline.as_secs_f64(), dup_count);
for &batch in &batches {
let mut sets = template.clone();
let mut dup_count2 = 0usize;
let t = Instant::now();
for chunk in ops.chunks(batch) {
for &(off, sig) in chunk {
let _ = sets[off as usize].contains(&sig);
}
for &(off, sig) in chunk {
if !sets[off as usize].insert(sig) {
dup_count2 += 1;
}
}
}
let elapsed = t.elapsed();
let speedup =
100.0 * (baseline.as_secs_f64() - elapsed.as_secs_f64()) / baseline.as_secs_f64();
eprintln!(
"contains-warmup B={:2}: {:.3}s ({:+.2}%) dups={}",
batch,
elapsed.as_secs_f64(),
speedup,
dup_count2,
);
assert_eq!(dup_count, dup_count2);
}
for &batch in &batches {
let mut sets = template.clone();
let mut dup_count3 = 0usize;
let t = Instant::now();
for chunk in ops.chunks(batch) {
for &(off, _) in chunk {
prefetch_read(&sets[off as usize] as *const U64Set);
}
for &(off, sig) in chunk {
if !sets[off as usize].insert(sig) {
dup_count3 += 1;
}
}
}
let elapsed = t.elapsed();
let speedup =
100.0 * (baseline.as_secs_f64() - elapsed.as_secs_f64()) / baseline.as_secs_f64();
eprintln!(
"prefetch-struct B={:2}: {:.3}s ({:+.2}%) dups={}",
batch,
elapsed.as_secs_f64(),
speedup,
dup_count3,
);
assert_eq!(dup_count, dup_count3);
}
}