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:
Niko Matsakis 2019-06-14 05:52:51 -04:00
parent 4e5a23b9d7
commit e9f91f03a5
4 changed files with 289 additions and 7 deletions

View file

@ -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"

View file

@ -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
View 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) = &current_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
View 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());
}