mirror of
https://github.com/salsa-rs/salsa.git
synced 2024-12-24 12:58:37 +00:00
roll our own lru list
We could use e.g. intrusive-collections but from reading the docs and surveying the source it wasn't *obvious* to me that it had the right semantics.
This commit is contained in:
parent
4e5a23b9d7
commit
e9f91f03a5
4 changed files with 289 additions and 7 deletions
|
@ -9,15 +9,16 @@ description = "A generic framework for on-demand, incrementalized computation (e
|
|||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
arc-swap = "0.3"
|
||||
derive-new = "0.5.5"
|
||||
rustc-hash = "1.0"
|
||||
parking_lot = "0.8.0"
|
||||
lock_api = "0.2.0"
|
||||
indexmap = "1.0.1"
|
||||
linked-hash-map = "0.5.2"
|
||||
lock_api = "0.2.0"
|
||||
log = "0.4.5"
|
||||
parking_lot = "0.8.0"
|
||||
rustc-hash = "1.0"
|
||||
smallvec = "0.6.5"
|
||||
salsa-macros = { version = "0.13.0", path = "components/salsa-macros" }
|
||||
linked-hash-map = "0.5.2"
|
||||
|
||||
[dev-dependencies]
|
||||
diff = "0.1.0"
|
||||
|
|
|
@ -12,6 +12,7 @@ mod derived;
|
|||
mod input;
|
||||
mod intern_id;
|
||||
mod interned;
|
||||
mod lru;
|
||||
mod runtime;
|
||||
|
||||
pub mod debug;
|
||||
|
@ -22,9 +23,9 @@ pub mod plumbing;
|
|||
|
||||
use crate::plumbing::CycleDetected;
|
||||
use crate::plumbing::InputQueryStorageOps;
|
||||
use crate::plumbing::LruQueryStorageOps;
|
||||
use crate::plumbing::QueryStorageMassOps;
|
||||
use crate::plumbing::QueryStorageOps;
|
||||
use crate::plumbing::LruQueryStorageOps;
|
||||
use derive_new::new;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::Hash;
|
||||
|
@ -547,8 +548,7 @@ where
|
|||
where
|
||||
Q::Storage: plumbing::LruQueryStorageOps,
|
||||
{
|
||||
self.storage
|
||||
.set_lru_capacity(cap);
|
||||
self.storage.set_lru_capacity(cap);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
164
src/lru.rs
Normal file
164
src/lru.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use arc_swap::ArcSwapOption;
|
||||
use arc_swap::Lease;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod test;
|
||||
|
||||
/// A very simple concurrent lru list, built using a doubly linked
|
||||
/// list of Arcs.
|
||||
///
|
||||
/// The list uses a very simple locking scheme and will probably
|
||||
/// suffer under high contention. This could certainly be improved.
|
||||
///
|
||||
/// We assume but do not verify that each node is only used with one
|
||||
/// list. If this is not the case, it is not *unsafe*, but panics and
|
||||
/// weird results will ensue.
|
||||
///
|
||||
/// Each "node" in the list is of type `Node` and must implement
|
||||
/// `LruNode`, which is a trait that gives access to a field of type
|
||||
/// `LruLinks<Node>`, which stores the prev/next points.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Lru<Node>
|
||||
where
|
||||
Node: LruNode,
|
||||
{
|
||||
len: usize,
|
||||
head: Option<Arc<Node>>,
|
||||
tail: Option<Arc<Node>>,
|
||||
}
|
||||
|
||||
pub(crate) trait LruNode: Sized + Debug {
|
||||
fn links(&self) -> &LruLinks<Self>;
|
||||
}
|
||||
|
||||
pub(crate) struct LruLinks<Node> {
|
||||
prev: ArcSwapOption<Node>,
|
||||
next: ArcSwapOption<Node>,
|
||||
}
|
||||
|
||||
impl<Node> Default for Lru<Node>
|
||||
where
|
||||
Node: LruNode,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Lru {
|
||||
len: 0,
|
||||
head: None,
|
||||
tail: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node> Drop for Lru<Node>
|
||||
where
|
||||
Node: LruNode,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node> Lru<Node>
|
||||
where
|
||||
Node: LruNode,
|
||||
{
|
||||
/// Removes everyting from the list.
|
||||
pub fn clear(&mut self) {
|
||||
// Not terribly efficient at the moment.
|
||||
while self.pop_lru().is_some() {}
|
||||
}
|
||||
|
||||
/// Removes the least-recently-used item in the list.
|
||||
pub fn pop_lru(&mut self) -> Option<Arc<Node>> {
|
||||
log::debug!("pop_lru(self={:?})", self);
|
||||
let node = self.tail.take()?;
|
||||
debug_assert!(node.links().next.load().is_none());
|
||||
self.tail = node.links().prev.swap(None);
|
||||
if let Some(new_tail) = &self.tail {
|
||||
new_tail.links().next.store(None);
|
||||
self.len -= 1;
|
||||
} else {
|
||||
self.head = None;
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
||||
/// Makes `node` the least-recently-used item in the list, adding
|
||||
/// it to the list if it was not already a member.
|
||||
pub fn promote(&mut self, node: Arc<Node>) -> usize {
|
||||
log::debug!("promote(node={:?})", node);
|
||||
let node = node.clone();
|
||||
|
||||
let node_links = node.links();
|
||||
|
||||
// First: check if the node is already in the linked list and has neighbors.
|
||||
// If so, let's unlink it.
|
||||
{
|
||||
let old_prev = node_links.prev.lease().into_option();
|
||||
let old_next = node_links.next.lease().into_option();
|
||||
log::debug!("promote: old_prev={:?}", old_prev);
|
||||
log::debug!("promote: old_next={:?}", old_next);
|
||||
match (old_prev, old_next) {
|
||||
(Some(old_prev), Some(old_next)) => {
|
||||
// Node is in the middle of the list.
|
||||
old_prev.links().next.store(Some(Lease::upgrade(&old_next)));
|
||||
old_next
|
||||
.links()
|
||||
.prev
|
||||
.store(Some(Lease::into_upgrade(old_prev)));
|
||||
self.len -= 1;
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
// Node is already at the head of the list. Nothing to do here.
|
||||
return self.len;
|
||||
}
|
||||
(Some(old_prev), None) => {
|
||||
// Node is at the tail of the (non-empty) list.
|
||||
old_prev.links().next.store(None);
|
||||
self.tail = Some(Lease::into_upgrade(old_prev));
|
||||
self.len -= 1;
|
||||
}
|
||||
(None, None) => {
|
||||
// Node is either not in the list *or* at the head of a singleton list.
|
||||
if let Some(head) = &self.head {
|
||||
if Arc::ptr_eq(head, &node) {
|
||||
// Node is at the head.
|
||||
return self.len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the node's links are stale but the node is not a member
|
||||
// of the list.
|
||||
let current_head: Option<Arc<Node>> = self.head.clone();
|
||||
if let Some(current_head) = ¤t_head {
|
||||
current_head.links().prev.store(Some(node.clone()));
|
||||
}
|
||||
node_links.next.store(current_head);
|
||||
node_links.prev.store(None);
|
||||
if self.len == 0 {
|
||||
self.tail = Some(node.clone());
|
||||
}
|
||||
self.head = Some(node);
|
||||
self.len += 1;
|
||||
return self.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node> Default for LruLinks<Node> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
prev: ArcSwapOption::default(),
|
||||
next: ArcSwapOption::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node> std::fmt::Debug for LruLinks<Node> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "LruLinks {{ .. }}")
|
||||
}
|
||||
}
|
117
src/lru/test.rs
Normal file
117
src/lru/test.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestNode {
|
||||
data: usize,
|
||||
links: LruLinks<TestNode>,
|
||||
}
|
||||
|
||||
impl TestNode {
|
||||
fn new(data: usize) -> Arc<Self> {
|
||||
Arc::new(TestNode {
|
||||
data,
|
||||
links: LruLinks::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LruNode for TestNode {
|
||||
fn links(&self) -> &LruLinks<TestNode> {
|
||||
&self.links
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue() {
|
||||
let mut lru = Lru::default();
|
||||
let n1 = TestNode::new(1);
|
||||
let n2 = TestNode::new(2);
|
||||
let n3 = TestNode::new(3);
|
||||
|
||||
assert!(lru.pop_lru().is_none());
|
||||
|
||||
assert_eq!(lru.promote(n1.clone()), 1);
|
||||
assert_eq!(lru.promote(n2.clone()), 2);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
|
||||
assert!(Arc::ptr_eq(&n1, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n2, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n3, &lru.pop_lru().unwrap()));
|
||||
assert!(lru.pop_lru().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_last() {
|
||||
let mut lru = Lru::default();
|
||||
let n1 = TestNode::new(1);
|
||||
let n2 = TestNode::new(2);
|
||||
let n3 = TestNode::new(3);
|
||||
|
||||
assert_eq!(lru.promote(n1.clone()), 1);
|
||||
assert_eq!(lru.promote(n2.clone()), 2);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
assert_eq!(lru.promote(n1.clone()), 3);
|
||||
|
||||
assert!(Arc::ptr_eq(&n2, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n3, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n1, &lru.pop_lru().unwrap()));
|
||||
assert!(lru.pop_lru().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_middle() {
|
||||
let mut lru = Lru::default();
|
||||
let n1 = TestNode::new(1);
|
||||
let n2 = TestNode::new(2);
|
||||
let n3 = TestNode::new(3);
|
||||
|
||||
assert_eq!(lru.promote(n1.clone()), 1);
|
||||
assert_eq!(lru.promote(n2.clone()), 2);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
assert_eq!(lru.promote(n2.clone()), 3);
|
||||
|
||||
assert!(Arc::ptr_eq(&n1, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n3, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n2, &lru.pop_lru().unwrap()));
|
||||
assert!(&lru.pop_lru().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_head() {
|
||||
let mut lru = Lru::default();
|
||||
let n1 = TestNode::new(1);
|
||||
let n2 = TestNode::new(2);
|
||||
let n3 = TestNode::new(3);
|
||||
|
||||
assert_eq!(lru.promote(n1.clone()), 1);
|
||||
assert_eq!(lru.promote(n2.clone()), 2);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
|
||||
assert!(Arc::ptr_eq(&n1, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n2, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n3, &lru.pop_lru().unwrap()));
|
||||
assert!(&lru.pop_lru().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_rev() {
|
||||
let mut lru = Lru::default();
|
||||
let n1 = TestNode::new(1);
|
||||
let n2 = TestNode::new(2);
|
||||
let n3 = TestNode::new(3);
|
||||
|
||||
assert_eq!(lru.promote(n1.clone()), 1);
|
||||
assert_eq!(lru.promote(n2.clone()), 2);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
assert_eq!(lru.promote(n3.clone()), 3);
|
||||
assert_eq!(lru.promote(n2.clone()), 3);
|
||||
assert_eq!(lru.promote(n1.clone()), 3);
|
||||
|
||||
assert!(Arc::ptr_eq(&n3, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n2, &lru.pop_lru().unwrap()));
|
||||
assert!(Arc::ptr_eq(&n1, &lru.pop_lru().unwrap()));
|
||||
assert!(&lru.pop_lru().is_none());
|
||||
}
|
Loading…
Reference in a new issue