Block/node eviction

This commit is contained in:
Daniël de Kok 2024-08-05 09:46:37 +00:00
parent ce76f4ccc3
commit 5da696046e
1 changed files with 128 additions and 6 deletions

View File

@ -16,6 +16,7 @@ use slotmap::{DefaultKey, SlotMap};
type NodeId = DefaultKey;
#[derive(Debug)]
pub struct RadixTrie {
root: DefaultKey,
leaves: BTreeSet<(u64, NodeId)>,
@ -45,8 +46,8 @@ impl RadixTrie {
let node = &self.nodes[node_id];
if let Some(&child_id) = node.children.get(&key[0]) {
let child = self.nodes.get_mut(child_id).unwrap();
child.last_accessed = self.time;
self.update_access_time(child_id);
let child = self.nodes.get(child_id).unwrap();
let shared_prefix_len = child.key.shared_prefix_len(key);
blocks.extend(&child.blocks[..shared_prefix_len]);
@ -60,6 +61,10 @@ impl RadixTrie {
}
pub fn decref(&mut self, node_id: NodeId) {
if node_id == self.root {
return;
}
let node = self.nodes.get_mut(node_id).unwrap();
assert!(node.ref_count > 0);
node.ref_count -= 1;
@ -68,7 +73,44 @@ impl RadixTrie {
}
}
/// Evict `n_blocks` from the trie.
///
/// Returns the evicted blocks.
pub fn evict(&mut self, n_blocks: usize) -> Vec<u32> {
let mut evicted = Vec::new();
while let Some((last_access, node_id)) = self.leaves.pop_first() {
let blocks_needed = n_blocks - evicted.len();
let node = self.nodes.get(node_id).unwrap();
if blocks_needed >= node.blocks.len() {
// We need to evict the whole node if we exhaust all its blocks.
let node = self.remove_node(node_id);
evicted.extend(node.blocks);
if evicted.len() >= n_blocks {
break;
}
} else {
// The node has more blocks than needed, so we'll just remove
// what is needed.
let node = self.nodes.get_mut(node_id).unwrap();
node.key.truncate(node.blocks.len() - blocks_needed);
evicted.extend(node.blocks.split_off(node.blocks.len() - blocks_needed));
self.leaves.insert((last_access, node_id));
break;
}
}
evicted
}
pub fn incref(&mut self, node_id: NodeId) {
if node_id == self.root {
return;
}
let node = self.nodes.get_mut(node_id).unwrap();
if node.ref_count == 0 {
self.leaves.remove(&(node.last_accessed, node_id));
@ -85,8 +127,8 @@ impl RadixTrie {
assert_eq!(key.len(), blocks.len());
if let Some(&child_id) = self.nodes[node_id].children.get(&key[0]) {
self.update_access_time(child_id);
let child = self.nodes.get_mut(child_id).unwrap();
child.last_accessed = self.time;
let shared_prefix_len = child.key.shared_prefix_len(key);
// We are done, the prefix is already in the trie.
@ -112,7 +154,7 @@ impl RadixTrie {
let blocks = &blocks[shared_prefix_len..];
self.insert_(child_id, key, blocks)
} else {
self.add_child(node_id, key, blocks);
self.add_node(node_id, key, blocks);
key.len()
}
}
@ -121,10 +163,10 @@ impl RadixTrie {
let node = self.nodes.get_mut(node_id).unwrap();
let rest_key = node.key.split_off(prefix_len);
let rest_blocks = node.blocks.split_off(prefix_len);
self.add_child(node_id, rest_key, rest_blocks);
self.add_node(node_id, rest_key, rest_blocks);
}
fn add_child(
fn add_node(
&mut self,
parent_id: NodeId,
key: impl Into<Vec<u32>>,
@ -139,9 +181,52 @@ impl RadixTrie {
let node = self.nodes.get_mut(parent_id).unwrap();
node.children.insert(first, child_id);
self.incref(parent_id);
self.leaves.insert((self.time, child_id));
}
fn remove_node(&mut self, node_id: NodeId) -> TrieNode {
let node = self.nodes.remove(node_id).unwrap();
let parent_id = node.parent.expect("Attempted to remove root node");
let parent = self.nodes.get_mut(parent_id).unwrap();
parent.children.remove(&node.key[0]);
self.decref(parent_id);
self.nodes.remove(node_id);
node
}
fn update_access_time(&mut self, node_id: NodeId) {
let node = self.nodes.get_mut(node_id).unwrap();
// Update the ordered leaves set if the node is a leave.
if self.leaves.remove(&(node.last_accessed, node_id)) {
self.leaves.insert((self.time, node_id));
}
node.last_accessed = self.time;
}
pub fn print_debug(&self) {
self.print_debug_(self.root, 0);
}
fn print_debug_(&self, node_id: NodeId, indent: usize) {
let node = &self.nodes[node_id];
eprintln!(
"{}{:?}, key: {:?}, blocks: {:?}, ref_count: {}, last_accessed: {}",
" ".repeat(indent),
node_id,
node.key,
node.blocks,
node.ref_count,
node.last_accessed
);
for child_id in self.nodes[node_id].children.values() {
self.print_debug_(*child_id, indent + 2);
}
}
}
#[derive(Debug)]
struct TrieNode {
blocks: Vec<u32>,
children: HashMap<u32, NodeId>,
@ -233,4 +318,41 @@ mod tests {
trie.find(&[0, 1, 2, 3, 5], &mut blocks);
assert_eq!(blocks, vec![0, 1, 2, 3, 5]);
}
#[test]
fn evict_removes_correct_blocks() {
let mut trie = super::RadixTrie::new();
trie.insert(&[0, 1, 2], &[0, 1, 2]);
trie.insert(&[0, 1, 2, 3, 5, 6, 7], &[0, 1, 2, 3, 5, 6, 7]);
trie.insert(&[0, 1, 2, 3, 4], &[0, 1, 2, 3, 4]);
trie.insert(&[1, 2, 3], &[1, 2, 3]);
let mut blocks = Vec::new();
// Remove less than the leave blocks.
assert_eq!(trie.evict(1), vec![7]);
trie.find(&[0, 1, 2, 3, 5, 6, 7], &mut blocks);
assert_eq!(blocks, vec![0, 1, 2, 3, 5, 6]);
// Refresh other leaf.
trie.find(&[0, 1, 2, 3, 4], &mut blocks);
trie.find(&[1, 2, 3], &mut blocks);
// Remove the leave blocks exactly.
assert_eq!(trie.evict(2), vec![5, 6]);
blocks.clear();
trie.find(&[0, 1, 2, 3, 5, 6, 7], &mut blocks);
assert_eq!(blocks, vec![0, 1, 2, 3]);
trie.find(&[1, 2, 3], &mut blocks);
// Remove more than the leave blocks.
assert_eq!(trie.evict(3), vec![4, 3, 2]);
blocks.clear();
trie.find(&[0, 1, 2, 3, 4], &mut blocks);
assert_eq!(blocks, vec![0, 1]);
// Clear out the whole trie.
assert_eq!(trie.evict(10), vec![1, 2, 3, 0, 1]);
}
}