pub(crate) const INITIAL_MEMO_SLOTS: usize = 1024;
const _: () = assert!(INITIAL_MEMO_SLOTS.is_power_of_two());
pub(crate) struct RewrittenNodes {
pub(crate) keys: Vec<u64>,
pub(crate) values: Vec<u64>,
pub(crate) len: usize,
}
impl RewrittenNodes {
pub(crate) fn new() -> Self {
Self {
keys: vec![0; INITIAL_MEMO_SLOTS],
values: vec![0; INITIAL_MEMO_SLOTS],
len: 0,
}
}
pub(crate) fn slot_of(&self, key: u64) -> usize {
let mixed = key.wrapping_mul(0x9E37_79B9_7F4A_7C15);
(mixed >> 32) as usize & (self.keys.len() - 1)
}
pub(crate) fn occupied_slots(&self) -> usize {
self.keys.iter().filter(|key| **key != 0).count()
}
pub(crate) fn get(&self, key: u64) -> Option<u64> {
let mut slot = self.slot_of(key);
loop {
match self.keys[slot] {
0 => return None,
found if found == key => return Some(self.values[slot]),
_ => slot = (slot + 1) & (self.keys.len() - 1),
}
}
}
pub(crate) fn insert(&mut self, key: u64, value: u64) {
if (self.len + 1) * 10 >= self.keys.len() * 7 {
self.grow();
}
self.insert_without_growing(key, value);
self.len += 1;
}
pub(crate) fn insert_without_growing(&mut self, key: u64, value: u64) {
let mut slot = self.slot_of(key);
while self.keys[slot] != 0 {
assert_ne!(
self.keys[slot], key,
"a source record was memoized twice; the memo probe or the \
path set is broken"
);
slot = (slot + 1) & (self.keys.len() - 1);
}
self.keys[slot] = key;
self.values[slot] = value;
}
pub(crate) fn grow(&mut self) {
let occupied: Vec<(u64, u64)> = self
.keys
.iter()
.zip(&self.values)
.filter(|(key, _)| **key != 0)
.map(|(key, value)| (*key, *value))
.collect();
self.keys = vec![0; self.keys.len() * 2];
self.values = vec![0; self.values.len() * 2];
for (key, value) in occupied {
self.insert_without_growing(key, value);
}
}
}
#[cfg(test)]
mod tests {
use crate::writer::compaction::deep_copy_tree_with_progress;
use crate::writer::compaction::test_support::*;
use crate::writer::store_writer::WritableRepository;
#[test]
fn the_exact_memo_costs_a_bounded_number_of_bytes_a_node() {
use crate::segment::identifier::SegmentIdentifier;
use crate::segment::record::RecordIdentifier;
use crate::writer::compaction::{RewrittenNodes, SegmentInterner};
for count in [1_000_000usize, 4_000_000] {
let mut interner = SegmentInterner::new();
let mut memo = RewrittenNodes::new();
for index in 0..count {
let record = RecordIdentifier {
segment: SegmentIdentifier {
most_significant_bits: (index / 8192) as u64,
least_significant_bits: 0x5eed,
},
record_number: index as u32,
};
let packed = interner.pack(record);
memo.insert(packed, packed);
assert_eq!(interner.unpack(packed), record, "packing round-trips");
}
for index in 0..count {
let record = RecordIdentifier {
segment: SegmentIdentifier {
most_significant_bits: (index / 8192) as u64,
least_significant_bits: 0x5eed,
},
record_number: index as u32,
};
let packed = interner.pack(record);
assert_eq!(
memo.get(packed),
Some(packed),
"entry {index} of {count} survives every growth"
);
}
let bytes_per_node = memo.keys.len() * 2 * std::mem::size_of::<u64>() / count;
assert_eq!(memo.len, count);
assert!(
bytes_per_node <= 48,
"{count} entries cost {bytes_per_node} bytes a node; the packed \
table must stay far below the ~110 an identifier-keyed map costs"
);
assert!(
memo.len * 10 <= memo.keys.len() * 7,
"the table stays under its load factor"
);
}
}
#[test]
fn the_exact_memo_holds_only_what_the_tree_reaches() {
for fanout in [100usize, 320] {
let directory = TestDirectory::new(&format!("footprint-{fanout}"));
build_wide_store(&directory, fanout);
let store = WritableRepository::open(&directory.path).expect("open");
let head = store.head();
let distinct = distinct_reachable_nodes(&store, head);
let generation = store.writing_generation().expect("generation");
let mut writer = store.record_writer(generation);
let (_root, copied) = deep_copy_tree_with_progress(
&store,
&mut writer,
head,
&mut crate::progress::DiscardedProgress,
)
.expect("deep copy");
writer.finish().expect("finish");
store.close().expect("close");
assert_eq!(copied as usize, distinct, "the copy is exact at {fanout}");
}
}
#[test]
fn the_memo_and_the_interner_hold_their_own_invariants() {
use crate::segment::identifier::SegmentIdentifier;
use crate::segment::record::RecordIdentifier;
use crate::writer::compaction::{RewrittenNodes, SegmentInterner};
let mut rng = Rng(0x5EED_1234_9ABC_DEF1);
let mut interner = SegmentInterner::new();
let mut memo = RewrittenNodes::new();
let mut expected = std::collections::HashMap::new();
let mut packed_seen = std::collections::HashMap::new();
for _ in 0..60_000 {
let record = RecordIdentifier {
segment: SegmentIdentifier {
most_significant_bits: rng.next() % 400,
least_significant_bits: rng.next() % 7,
},
record_number: (rng.next() % 5000) as u32,
};
let packed = interner.pack(record);
assert_ne!(packed, 0, "no real record packs to the empty-slot key");
assert_eq!(interner.unpack(packed), record, "packing round-trips");
if let Some(previous) = packed_seen.insert(packed, record) {
assert_eq!(previous, record, "two distinct records packed alike");
}
if let std::collections::hash_map::Entry::Vacant(slot) = expected.entry(packed) {
let value = interner.pack(RecordIdentifier {
segment: record.segment,
record_number: record.record_number ^ 0x00FF_00FF,
});
slot.insert(value);
memo.insert(packed, value);
}
assert_eq!(memo.len, expected.len());
for (key, value) in &expected {
assert_eq!(memo.get(*key), Some(*value));
}
if expected.len() > 40 {
expected.clear();
memo = RewrittenNodes::new();
}
}
}
}