use std::collections::HashMap;
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>()
}
const COUNT_BITS: u32 = 8;
const COUNT_MASK: u64 = (1 << COUNT_BITS) - 1;
const SPILLED: u64 = COUNT_MASK;
const UNHASH: u64 = inverse(0x9E37_79B9_7F4A_7C15);
const fn inverse(odd: u64) -> u64 {
let mut inverse = odd;
let mut step = 0;
while step < 5 {
inverse = inverse.wrapping_mul(2_u64.wrapping_sub(odd.wrapping_mul(inverse)));
step += 1;
}
inverse
}
#[derive(Debug)]
pub(crate) struct ExactCounts {
sets: Vec<Vec<u64>>,
held: Vec<usize>,
buffered: Vec<u64>,
waiting: Vec<u8>,
spilled: HashMap<u64, u64, crate::Spread>,
len: usize,
gave_up: bool,
}
impl ExactCounts {
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],
spilled: HashMap::default(),
len: 0,
gave_up: false,
}
}
pub(crate) fn insert(&mut self, value: u64, times: u32) {
if self.gave_up || times == 0 {
return;
}
let hash = hash(value);
let set = (hash >> (64 - SET_BITS)) as usize;
if times > 1 {
self.add(set, hash, u64::from(times));
self.check_cap();
return;
}
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> {
self.drain_all();
(!self.gave_up).then_some(self.len as u64)
}
pub(crate) fn visit(&mut self, mut visit: impl FnMut(u64, u64)) -> bool {
self.drain_all();
if self.gave_up {
return false;
}
for (set, slots) in self.sets.iter().enumerate() {
for &slot in slots.iter().filter(|&&slot| slot != 0) {
let hash = ((set as u64) << (64 - SET_BITS)) | ((slot & !COUNT_MASK) >> SET_BITS);
let count = match slot & COUNT_MASK {
SPILLED => self.spilled.get(&hash).copied().unwrap_or(SPILLED),
count => count,
};
visit(hash.wrapping_mul(UNHASH), count);
}
}
true
}
fn drain_all(&mut self) {
for set in 0..SETS {
if self.gave_up {
break;
}
self.drain(set);
}
}
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];
self.add(set, hash, 1);
}
self.check_cap();
}
fn add(&mut self, set: usize, hash: u64, times: u64) {
let key = hash << SET_BITS;
let slots = &mut self.sets[set];
let mask = slots.len() - 1;
let mut at = home(slots, key);
loop {
let slot = slots[at];
if slot == 0 {
slots[at] = if times >= SPILLED {
self.spilled.insert(hash, times);
key | SPILLED
} else {
key | times
};
self.held[set] += 1;
self.len += 1;
if full(self.held[set], slots.len()) {
let wanted = slots.len() * 2;
let old = std::mem::replace(slots, vec![0; wanted]);
for slot in old.into_iter().filter(|&slot| slot != 0) {
place(slots, slot);
}
}
return;
}
if slot & !COUNT_MASK == key {
let count = slot & COUNT_MASK;
if count == SPILLED {
*self.spilled.entry(hash).or_insert(SPILLED) += times;
} else if count + times >= SPILLED {
self.spilled.insert(hash, count + times);
slots[at] = key | SPILLED;
} else {
slots[at] = slot + times;
}
return;
}
at = (at + 1) & mask;
}
}
fn check_cap(&mut self) {
if self.len > MAX_DISTINCT {
self.gave_up = true;
self.sets = Vec::new();
self.buffered = Vec::new();
self.spilled = HashMap::default();
}
}
}
fn touch(slots: &[u64], hashes: &[u64]) {
let mut seen = 0_u64;
for &hash in hashes {
seen ^= slots[home(slots, hash << SET_BITS)];
}
std::hint::black_box(seen);
}
fn home(slots: &[u64], key: u64) -> usize {
(key >> (64 - slots.len().trailing_zeros())) as usize
}
fn place(slots: &mut [u64], slot: u64) {
let mask = slots.len() - 1;
let mut at = home(slots, slot & !COUNT_MASK);
while slots[at] != 0 {
at = (at + 1) & mask;
}
slots[at] = slot;
}
#[derive(Debug)]
pub(crate) struct DenseCounts {
low: u64,
counts: Vec<u32>,
outside: bool,
}
impl DenseCounts {
pub(crate) fn new(low: u64, len: usize) -> Self {
Self { low, counts: vec![0; len], outside: false }
}
pub(crate) fn insert(&mut self, value: u64, times: u32) {
let at = value.wrapping_sub(self.low);
match usize::try_from(at).ok().and_then(|at| self.counts.get_mut(at)) {
Some(count) => *count += times,
None => self.outside = true,
}
}
pub(crate) fn count(&self) -> Option<Option<u64>> {
if self.outside {
return None;
}
let distinct = self.counts.iter().filter(|&&count| count != 0).count();
Some((distinct <= MAX_DISTINCT).then_some(distinct as u64))
}
pub(crate) fn visit(&self, mut visit: impl FnMut(u64, u64)) {
for (at, &count) in self.counts.iter().enumerate().filter(|(_, count)| **count != 0) {
visit(self.low.wrapping_add(at as u64), u64::from(count));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn counted(set: &mut ExactCounts) -> HashMap<u64, u64> {
let mut counts = HashMap::new();
assert!(set.visit(|value, count| assert!(counts.insert(value, count).is_none())));
counts
}
#[test]
fn dense_counts_agree_with_the_set_and_notice_a_value_outside() {
let low = (-40_i64) as u64;
let mut dense = DenseCounts::new(low, 100);
let mut set = ExactCounts::new();
for at in 0..10_000_i64 {
let value = (at * 37 % 97 - 40) as u64;
let times = u32::try_from(at % 3 + 1).expect("small");
dense.insert(value, times);
set.insert(value, times);
}
assert_eq!(dense.count(), Some(set.count()));
let mut counts = HashMap::new();
dense.visit(|value, count| assert!(counts.insert(value, count).is_none()));
assert_eq!(counts, counted(&mut set));
dense.insert(60, 1);
assert_eq!(dense.count(), None);
}
#[test]
fn a_hash_turns_back_into_its_value() {
for value in [0, 1, 2, u64::MAX, 1 << 63, 0x0123_4567_89AB_CDEF] {
assert_eq!(hash(value).wrapping_mul(UNHASH), value);
}
}
#[test]
fn counts_each_value_and_its_rows_across_growth_and_counts_zero() {
let mut set = ExactCounts::new();
let mut oracle = HashMap::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, 1);
*oracle.entry(bits).or_insert(0) += 1;
}
}
assert_eq!(set.count(), Some(oracle.len() as u64), "round {round} counted wrong");
assert_eq!(counted(&mut set), oracle, "round {round} counted the rows wrong");
}
}
#[test]
fn a_count_past_a_slot_moves_beside_the_set_and_keeps_counting() {
let mut set = ExactCounts::new();
for _ in 0..300 {
set.insert(7, 1);
}
set.insert(9, 254);
set.insert(9, 1);
set.insert(11, 1_000);
set.insert(11, 3);
for _ in 0..254 {
set.insert(13, 1);
}
let counts = counted(&mut set);
assert_eq!(counts, [(7, 300), (9, 255), (11, 1_003), (13, 254)].into_iter().collect());
assert_eq!(set.count(), Some(4));
}
#[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 = ExactCounts::new();
for value in 1..=distinct as u64 {
set.insert(value.wrapping_mul(0x0123_4567_89AB_CDEF), 1);
}
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"
);
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 = ExactCounts::new();
for value in 0..MAX_DISTINCT as u64 {
set.insert(value, 1);
}
assert_eq!(set.count(), Some(MAX_DISTINCT as u64), "gave up before the cap");
set.insert(MAX_DISTINCT as u64, 2);
assert_eq!(set.count(), None, "counted past the cap");
assert!(set.sets.is_empty(), "a column that gave up still holds its table");
assert!(!set.visit(|_, _| panic!("a column that gave up handed over a value")));
}
}