use std::collections::HashMap;
use std::hash::{BuildHasher, Hasher};
use std::sync::{Arc, OnceLock};
use crate::Name;
const LINEAR_LOOKUP_MAX: usize = 8;
#[inline]
pub(crate) fn process_hash_seed() -> u64 {
static SEED: OnceLock<u64> = OnceLock::new();
*SEED.get_or_init(|| {
std::collections::hash_map::RandomState::new().hash_one(0x5eed_u64) | 1
})
}
#[derive(Clone, Copy)]
pub(crate) struct NameHasher(u64);
#[derive(Clone, Copy)]
pub(crate) struct NameState(u64);
impl Default for NameState {
#[inline]
fn default() -> Self {
Self(0xcbf2_9ce4_8422_2325 ^ process_hash_seed())
}
}
impl BuildHasher for NameState {
type Hasher = NameHasher;
#[inline]
fn build_hasher(&self) -> NameHasher {
NameHasher(self.0)
}
}
impl Hasher for NameHasher {
#[inline]
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3);
}
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}
#[derive(Clone, Default)]
pub(crate) struct NameDict {
inner: Arc<Inner>,
}
#[derive(Clone, Default)]
struct Inner {
names: Vec<Name>,
ids: HashMap<Name, u32, NameState>,
}
impl NameDict {
#[inline]
pub(crate) fn id_of(&self, name: &str) -> Option<u32> {
let inner = &*self.inner;
if inner.names.len() <= LINEAR_LOOKUP_MAX {
inner
.names
.iter()
.position(|n| n.as_str() == name)
.map(|i| i as u32)
} else {
inner.ids.get(name).copied()
}
}
pub(crate) fn id_or_insert(&mut self, name: &Name) -> u32 {
self.id_or_insert_with(name, || name.clone())
}
pub(crate) fn id_or_insert_with(&mut self, name: &str, make: impl FnOnce() -> Name) -> u32 {
if let Some(id) = self.id_of(name) {
return id;
}
let name = make();
let inner = Arc::make_mut(&mut self.inner);
let id = inner.names.len() as u32;
inner.names.push(name.clone());
inner.ids.insert(name, id);
id
}
#[cfg(test)]
pub(crate) fn id_or_insert_str(&mut self, name: &str) -> u32 {
match self.id_of(name) {
Some(id) => id,
None => self.id_or_insert(&Name::new(name)),
}
}
#[inline]
pub(crate) fn name(&self, id: u32) -> &Name {
&self.inner.names[id as usize]
}
pub(crate) fn len(&self) -> usize {
self.inner.names.len()
}
pub(crate) fn heap_bytes(&self) -> usize {
let inner = &*self.inner;
inner.names.capacity() * std::mem::size_of::<Name>()
+ inner.ids.capacity() * (std::mem::size_of::<Name>() + std::mem::size_of::<u32>() + 1)
}
}
#[derive(Clone, Default)]
pub(crate) struct Dicts {
pub(crate) labels: NameDict,
pub(crate) types: NameDict,
pub(crate) keys: NameDict,
pub(crate) values: ValueDict,
}
impl Dicts {
pub(crate) fn heap_bytes(&self) -> usize {
self.labels.heap_bytes()
+ self.types.heap_bytes()
+ self.keys.heap_bytes()
+ self.values.heap_bytes()
}
}
const VALUE_MAX_LEN: usize = 32;
const TRIAL_VALUES: u16 = 64;
const TRIAL_DISTINCT: usize = 8;
const KEY_MISSES: u16 = 64;
const VALUES_MAX: usize = 4096;
#[derive(Clone, Default)]
pub(crate) struct ValueDict {
strings: NameDict,
keys: Arc<Vec<KeyValues>>,
}
#[derive(Clone)]
enum KeyValues {
Trial {
seen: u16,
distinct: Vec<u64>,
},
Numbered {
misses: u16,
},
Stored,
}
impl Default for KeyValues {
fn default() -> Self {
KeyValues::Trial {
seen: 0,
distinct: Vec::new(),
}
}
}
impl ValueDict {
#[inline]
pub(crate) fn number(&mut self, key: u32, value: &str) -> Option<u32> {
if value.len() > VALUE_MAX_LEN {
return None;
}
match self.keys.get(key as usize) {
Some(KeyValues::Stored) => None,
Some(KeyValues::Numbered { misses }) => {
if let Some(id) = self.strings.id_of(value) {
return Some(id);
}
if *misses >= KEY_MISSES || self.strings.len() >= VALUES_MAX {
return None;
}
let keys = Arc::make_mut(&mut self.keys);
if let KeyValues::Numbered { misses } = &mut keys[key as usize] {
*misses += 1;
}
Some(
self.strings
.id_or_insert_with(value, || crate::Name::from_arc(Arc::from(value))),
)
}
_ => {
self.observe(key, value);
None
}
}
}
fn observe(&mut self, key: u32, value: &str) {
let keys = Arc::make_mut(&mut self.keys);
if keys.len() <= key as usize {
keys.resize_with(key as usize + 1, KeyValues::default);
}
let state = &mut keys[key as usize];
let KeyValues::Trial { seen, distinct } = state else {
return;
};
let hash = NameState::default().hash_one(value);
if !distinct.contains(&hash) {
if distinct.len() == TRIAL_DISTINCT {
*state = KeyValues::Stored;
return;
}
distinct.push(hash);
}
*seen += 1;
if *seen == TRIAL_VALUES {
*state = KeyValues::Numbered { misses: 0 };
}
}
#[inline]
pub(crate) fn string(&self, id: u32) -> &str {
self.strings.name(id).as_str()
}
pub(crate) fn heap_bytes(&self) -> usize {
let strings: usize = (0..self.strings.len() as u32)
.map(|id| 2 * std::mem::size_of::<usize>() + self.string(id).len())
.sum();
self.strings.heap_bytes()
+ strings
+ self.keys.capacity() * std::mem::size_of::<KeyValues>()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn values_are_numbered_only_for_keys_that_repeat_a_few() {
let mut values = ValueDict::default();
for i in 0..TRIAL_VALUES {
assert_eq!(values.number(0, ["a", "b", "c"][i as usize % 3]), None);
assert_eq!(values.number(1, &format!("id-{i}")), None);
}
let a = values.number(0, "a").expect("key 0 is numbered");
assert_eq!(values.string(a), "a");
assert_eq!(values.number(0, "a"), Some(a));
assert_eq!(values.number(1, "id-1"), None);
assert_eq!(values.number(0, &"x".repeat(VALUE_MAX_LEN + 1)), None);
let added = (0..2 * KEY_MISSES)
.filter(|i| values.number(0, &format!("new-{i}")).is_some())
.count();
assert_eq!(added, KEY_MISSES as usize - 1);
assert_eq!(values.number(0, "a"), Some(a));
}
#[test]
fn names_are_numbered_once_in_both_lookup_modes() {
let mut dict = NameDict::default();
let names: Vec<Name> = (0..20).map(|i| Name::new(&format!("N{i}"))).collect();
for (i, name) in names.iter().enumerate() {
assert_eq!(dict.id_or_insert(name), i as u32);
for (j, earlier) in names[..=i].iter().enumerate() {
assert_eq!(dict.id_of(earlier), Some(j as u32));
assert_eq!(dict.name(j as u32), earlier);
}
}
assert_eq!(dict.id_or_insert(&names[3]), 3);
assert_eq!(dict.id_or_insert_str("N7"), 7);
assert_eq!(dict.id_or_insert_str("fresh"), 20);
assert_eq!(dict.id_of("missing"), None);
assert_eq!(dict.len(), 21);
}
#[test]
fn a_clone_shares_the_table_until_one_side_adds_a_name() {
let mut dict = NameDict::default();
dict.id_or_insert_str("A");
let mut copy = dict.clone();
assert_eq!(copy.id_or_insert_str("B"), 1);
assert_eq!(dict.id_of("B"), None);
assert_eq!(dict.id_or_insert_str("C"), 1);
assert_eq!(copy.name(1).as_str(), "B");
assert_eq!(dict.name(1).as_str(), "C");
}
#[test]
fn hashers_are_seeded_per_process() {
use std::hash::BuildHasher;
let unseeded = {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &b in b"name" {
h = (h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3);
}
h
};
let mut hasher = NameState::default().build_hasher();
hasher.write(b"name");
assert_ne!(hasher.finish(), unseeded);
assert_eq!(
NameState::default().hash_one("name"),
NameState::default().hash_one("name")
);
}
}