Skip to content

Commit fc8adfc

Browse files
committed
refactor: reorganize the LruCache implementation
Hopefully this makes it easier to follow and removes some duplication.
1 parent 0fcc361 commit fc8adfc

File tree

1 file changed

+94
-64
lines changed

1 file changed

+94
-64
lines changed

stacks-common/src/util/lru_cache.rs

Lines changed: 94 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -83,30 +83,9 @@ impl<K: Eq + std::hash::Hash + Clone, V: Copy> LruCache<K, V> {
8383

8484
/// Get the value for the given key
8585
pub fn get(&mut self, key: &K) -> Option<V> {
86-
if let Some(node) = self.cache.get(key) {
87-
// Move the node to the head of the LRU list
88-
let node = *node;
89-
90-
if node != self.head {
91-
let prev = self.order[node].prev;
92-
let next = self.order[node].next;
93-
94-
if node == self.tail {
95-
// If this is the tail, update the tail
96-
self.tail = prev;
97-
} else {
98-
// Else, update the next node's prev pointer
99-
self.order[next].prev = prev;
100-
}
101-
102-
self.order[prev].next = next;
103-
self.order[node].prev = self.capacity;
104-
self.order[node].next = self.head;
105-
self.order[self.head].prev = node;
106-
self.head = node;
107-
}
108-
109-
Some(self.order[node].value)
86+
if let Some(&index) = self.cache.get(key) {
87+
self.move_to_head(index);
88+
Some(self.order[index].value)
11089
} else {
11190
None
11291
}
@@ -127,75 +106,60 @@ impl<K: Eq + std::hash::Hash + Clone, V: Copy> LruCache<K, V> {
127106
/// Insert a key-value pair into the cache
128107
/// Returns `Some((K, V))` if a dirty value was evicted.
129108
pub fn insert_with_dirty(&mut self, key: K, value: V, dirty: bool) -> Option<(K, V)> {
130-
let mut evicted = None;
131-
if let Some(node) = self.cache.get(&key) {
132-
// Update the value for the key
133-
let node = *node;
134-
self.order[node].value = value;
135-
self.order[node].dirty = dirty;
136-
137-
// Just call get to handle updating the LRU list
138-
self.get(&key);
109+
if let Some(&index) = self.cache.get(&key) {
110+
// Update an existing node
111+
self.order[index].value = value;
112+
self.order[index].dirty = dirty;
113+
self.move_to_head(index);
114+
None
139115
} else {
116+
let mut evicted = None;
117+
// This is a new key
140118
let index = if self.cache.len() == self.capacity {
141-
// Take the place of the least recently used element.
142-
// First, remove it from the tail of the LRU list
143-
let index = self.tail;
144-
let prev = self.order[index].prev;
145-
self.order[prev].next = self.capacity;
146-
self.tail = prev;
147-
148-
// Remove it from the cache
149-
self.cache.remove(&self.order[index].key);
119+
// We've reached capacity. Evict the least-recently used value
120+
// and reuse its node
121+
let index = self.evict_lru();
150122

151123
// Replace the key with the new key, saving the old key
152124
let replaced_key = std::mem::replace(&mut self.order[index].key, key.clone());
153125

154-
// If it is dirty, save the key-value pair to return
126+
// Save the evicted key-value pair, if it was dirty
155127
if self.order[index].dirty {
156128
evicted = Some((replaced_key, self.order[index].value));
157-
}
158-
159-
// Insert this new value into the cache
160-
self.cache.insert(key, index);
129+
};
161130

162-
// Update the node with the new key-value pair, inserting it at
163-
// the head of the LRU list
131+
// Update the evicted node with the new key-value pair
164132
self.order[index].value = value;
165133
self.order[index].dirty = dirty;
166-
self.order[index].next = self.head;
167-
self.order[index].prev = self.capacity;
134+
135+
// Insert the new key-value pair into the cache
136+
self.cache.insert(key.clone(), index);
168137

169138
index
170139
} else {
171-
// Insert a new key-value pair
140+
// Create a new node, add it to the cache
141+
let index = self.order.len();
172142
let node = Node {
173143
key: key.clone(),
174144
value,
175145
dirty,
176-
next: self.head,
146+
next: self.capacity,
177147
prev: self.capacity,
178148
};
179-
180-
let index = self.order.len();
181149
self.order.push(node);
182150
self.cache.insert(key, index);
183-
184151
index
185152
};
186153

187-
// Put it at the head of the LRU list
188-
if self.head != self.capacity {
189-
self.order[self.head].prev = index;
190-
} else {
191-
self.tail = index;
192-
}
154+
// Put the new or reused node at the head of the LRU list
155+
self.attach_as_head(index);
193156

194-
self.head = index;
157+
evicted
195158
}
196-
evicted
197159
}
198160

161+
/// Flush all dirty values in the cache, calling the given function, `f`,
162+
/// for each dirty value.
199163
pub fn flush<E>(&mut self, mut f: impl FnMut(&K, V) -> Result<(), E>) -> Result<(), E> {
200164
let mut index = self.head;
201165
while index != self.capacity {
@@ -209,6 +173,72 @@ impl<K: Eq + std::hash::Hash + Clone, V: Copy> LruCache<K, V> {
209173
}
210174
Ok(())
211175
}
176+
177+
/// Helper function to remove a node from the linked list (by index)
178+
fn detach_node(&mut self, index: usize) {
179+
if index >= self.order.len() {
180+
return;
181+
}
182+
183+
let prev = self.order[index].prev;
184+
let next = self.order[index].next;
185+
186+
if index == self.tail {
187+
// If this is the last node, update the tail to point to its previous node
188+
self.tail = prev;
189+
} else {
190+
// Else, update the next node to point to the previous node
191+
self.order[next].prev = prev;
192+
}
193+
194+
if index == self.head {
195+
// If this is the first node, update the head to point to the next node
196+
self.head = next;
197+
} else {
198+
// Else, update the previous node to point to the next node
199+
self.order[prev].next = next;
200+
}
201+
}
202+
203+
/// Helper function to attach a node as the head of the linked list
204+
fn attach_as_head(&mut self, index: usize) {
205+
self.order[index].prev = self.capacity;
206+
self.order[index].next = self.head;
207+
208+
if self.head != self.capacity {
209+
// If there is a head, update its previous pointer to this one
210+
self.order[self.head].prev = index;
211+
} else {
212+
// Else, the list was empty, so update the tail
213+
self.tail = index;
214+
}
215+
self.head = index;
216+
}
217+
218+
/// Helper function to move a node to the head of the linked list
219+
fn move_to_head(&mut self, index: usize) {
220+
if index == self.head {
221+
// If the node is already the head, do nothing
222+
return;
223+
}
224+
225+
self.detach_node(index);
226+
self.attach_as_head(index);
227+
}
228+
229+
/// Helper function to evict the least-recently used node, which is the
230+
/// tail of the linked list
231+
/// Returns the index of the evicted node
232+
fn evict_lru(&mut self) -> usize {
233+
let index = self.tail;
234+
if index == self.capacity {
235+
// If the list is empty, do nothing
236+
return self.capacity;
237+
}
238+
self.detach_node(index);
239+
self.cache.remove(&self.order[index].key);
240+
index
241+
}
212242
}
213243

214244
#[cfg(test)]

0 commit comments

Comments
 (0)