From e9f91f03a597896d71cc1c7df02cd2a2d95a8f65 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 14 Jun 2019 05:52:51 -0400 Subject: [PATCH] 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. --- Cargo.toml | 9 +-- src/lib.rs | 6 +- src/lru.rs | 164 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lru/test.rs | 117 ++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 src/lru.rs create mode 100644 src/lru/test.rs diff --git a/Cargo.toml b/Cargo.toml index a709d4fa..6d1d26fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/lib.rs b/src/lib.rs index 2872c3e3..eb7959e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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); } } diff --git a/src/lru.rs b/src/lru.rs new file mode 100644 index 00000000..f5c42168 --- /dev/null +++ b/src/lru.rs @@ -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`, which stores the prev/next points. +#[derive(Debug)] +pub(crate) struct Lru +where + Node: LruNode, +{ + len: usize, + head: Option>, + tail: Option>, +} + +pub(crate) trait LruNode: Sized + Debug { + fn links(&self) -> &LruLinks; +} + +pub(crate) struct LruLinks { + prev: ArcSwapOption, + next: ArcSwapOption, +} + +impl Default for Lru +where + Node: LruNode, +{ + fn default() -> Self { + Lru { + len: 0, + head: None, + tail: None, + } + } +} + +impl Drop for Lru +where + Node: LruNode, +{ + fn drop(&mut self) { + self.clear(); + } +} + +impl Lru +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> { + 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) -> 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> = 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 Default for LruLinks { + fn default() -> Self { + Self { + prev: ArcSwapOption::default(), + next: ArcSwapOption::default(), + } + } +} + +impl std::fmt::Debug for LruLinks { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "LruLinks {{ .. }}") + } +} diff --git a/src/lru/test.rs b/src/lru/test.rs new file mode 100644 index 00000000..0edb1614 --- /dev/null +++ b/src/lru/test.rs @@ -0,0 +1,117 @@ +#![cfg(test)] + +use super::*; + +#[derive(Debug)] +struct TestNode { + data: usize, + links: LruLinks, +} + +impl TestNode { + fn new(data: usize) -> Arc { + Arc::new(TestNode { + data, + links: LruLinks::default(), + }) + } +} + +impl LruNode for TestNode { + fn links(&self) -> &LruLinks { + &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()); +}