const MAX_SLOTS: usize = 1 << 25;
const MAX_DISTINCT: usize = MAX_SLOTS / 8 * 7;
const SETS: usize = 1 << SET_BITS;
const SET_BITS: u32 = 8;
const BUFFERED: usize = 64;
const FIRST_SLOTS: usize = 1 << 4;
fn full(len: usize, slots: usize) -> bool {
len * 8 >= slots * 7
}
fn hash(value: u64) -> u64 {
value.wrapping_mul(0x9E37_79B9_7F4A_7C15)
}
pub(crate) fn beyond(estimate: f64) -> bool {
estimate > (2 * MAX_DISTINCT) as f64
}
pub(crate) fn bytes_for(estimate: f64) -> usize {
let values = estimate.clamp(0.0, MAX_DISTINCT as f64) as usize;
let share = (values / SETS).saturating_mul(9) / 8 + 1;
let mut slots = FIRST_SLOTS;
while full(share, slots) {
slots *= 2;
}
(slots + BUFFERED) * SETS * size_of::<u64>()
}
#[derive(Debug)]
pub(crate) struct ExactDistinct {
sets: Vec<Vec<u64>>,
held: Vec<usize>,
buffered: Vec<u64>,
waiting: Vec<u8>,
zero: bool,
len: usize,
gave_up: bool,
}
impl ExactDistinct {
pub(crate) fn new() -> Self {
Self {
sets: vec![vec![0; FIRST_SLOTS]; SETS],
held: vec![0; SETS],
buffered: vec![0; SETS * BUFFERED],
waiting: vec![0; SETS],
zero: false,
len: 0,
gave_up: false,
}
}
pub(crate) fn declined() -> Self {
Self {
sets: Vec::new(),
held: Vec::new(),
buffered: Vec::new(),
waiting: Vec::new(),
zero: false,
len: 0,
gave_up: true,
}
}
pub(crate) fn insert(&mut self, value: u64) {
if self.gave_up {
return;
}
if value == 0 {
self.zero = true;
return;
}
let hash = hash(value);
let set = (hash >> (64 - SET_BITS)) as usize;
let waiting = usize::from(self.waiting[set]);
self.buffered[set * BUFFERED + waiting] = hash;
self.waiting[set] = (waiting + 1) as u8;
if waiting + 1 == BUFFERED {
self.drain(set);
}
}
pub(crate) fn count(&mut self) -> Option<u64> {
for set in 0..SETS {
if self.gave_up {
break;
}
self.drain(set);
}
(!self.gave_up).then(|| self.len as u64 + u64::from(self.zero))
}
fn drain(&mut self, set: usize) {
let waiting = usize::from(std::mem::take(&mut self.waiting[set]));
let from = set * BUFFERED;
touch(&self.sets[set], &self.buffered[from..from + waiting]);
for at in from..from + waiting {
let hash = self.buffered[at];
if !place(&mut self.sets[set], hash) {
continue;
}
self.held[set] += 1;
self.len += 1;
if full(self.held[set], self.sets[set].len()) {
let wanted = self.sets[set].len() * 2;
let old = std::mem::replace(&mut self.sets[set], vec![0; wanted]);
for hash in old.into_iter().filter(|&hash| hash != 0) {
place(&mut self.sets[set], hash);
}
}
}
if self.len >= MAX_DISTINCT {
self.gave_up = true;
self.sets = Vec::new();
self.buffered = Vec::new();
}
}
}
fn touch(slots: &[u64], hashes: &[u64]) {
let mut seen = 0_u64;
for &hash in hashes {
seen ^= slots[home(slots, hash)];
}
std::hint::black_box(seen);
}
fn home(slots: &[u64], hash: u64) -> usize {
((hash << SET_BITS) >> (64 - slots.len().trailing_zeros())) as usize
}
fn place(slots: &mut [u64], hash: u64) -> bool {
let mask = slots.len() - 1;
let mut at = home(slots, hash);
loop {
match slots[at] {
0 => {
slots[at] = hash;
return true;
}
held if held == hash => return false,
_ => at = (at + 1) & mask,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_each_value_once_across_growth_and_counts_zero() {
let mut set = ExactDistinct::new();
let mut oracle = std::collections::HashSet::new();
for round in 0..3 {
for value in 0..50_000_u64 {
for bits in [value.wrapping_mul(0x0123_4567_89AB_CDEF), (-(value as i64)) as u64] {
set.insert(bits);
oracle.insert(bits);
}
}
assert_eq!(set.count(), Some(oracle.len() as u64), "round {round} counted wrong");
}
}
#[test]
fn the_estimate_covers_what_a_set_holds_and_not_much_more() {
for distinct in [0_usize, 1, 1_000, 40_000, 300_000, 1_000_000] {
let mut set = ExactDistinct::new();
for value in 1..=distinct as u64 {
set.insert(value.wrapping_mul(0x0123_4567_89AB_CDEF));
}
assert_eq!(set.count(), Some(distinct as u64));
let held = (set.sets.iter().map(Vec::len).sum::<usize>() + set.buffered.len()) * 8;
let estimate = bytes_for(distinct as f64);
assert!(held <= estimate, "{distinct} values held {held} bytes over {estimate}");
assert!(
estimate <= 2 * held,
"{distinct} values held {held} bytes, far under {estimate}"
);
}
assert!(
bytes_for(1e12) <= (512 << 20) + (1 << 20),
"past the cap is not the most a set holds"
);
}
#[test]
fn a_declined_set_counts_nothing() {
let mut set = ExactDistinct::declined();
set.insert(7);
set.insert(0);
assert_eq!(set.count(), None);
assert!(beyond(3.0 * MAX_DISTINCT as f64));
assert!(!beyond(MAX_DISTINCT as f64));
}
#[test]
fn a_column_past_the_cap_records_nothing() {
let mut set = ExactDistinct::new();
for value in 1..MAX_DISTINCT as u64 {
set.insert(value);
}
set.insert(0);
assert_eq!(set.count(), Some(MAX_DISTINCT as u64), "gave up before the cap");
set.insert(MAX_DISTINCT as u64);
assert_eq!(set.count(), None, "counted past the cap");
assert!(set.sets.is_empty(), "a column that gave up still holds its table");
}
}