From 50bb6acdde371e043a2230d3999157bfe7f3282f Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 19 Mar 2022 22:39:27 -0400 Subject: [PATCH 1/4] Serde support for LruCache --- src/lru_cache.rs | 71 +++++++++++++++++---- src/serde.rs | 157 ++++++++++++++++++++++++++++++++++++++++++++++- tests/serde.rs | 121 +++++++++++++++++++++++++++++++++++- 3 files changed, 330 insertions(+), 19 deletions(-) diff --git a/src/lru_cache.rs b/src/lru_cache.rs index 9ef36b5..e745689 100644 --- a/src/lru_cache.rs +++ b/src/lru_cache.rs @@ -14,12 +14,19 @@ pub use crate::linked_hash_map::{ RawOccupiedEntryMut, RawVacantEntryMut, VacantEntry, }; -pub struct LruCache { - map: LinkedHashMap, - max_size: usize, +pub struct LruCache +where + K: Eq + Hash, + S: BuildHasher + Default, +{ + pub(crate) map: LinkedHashMap, + pub(crate) max_size: usize, } -impl LruCache { +impl LruCache +where + K: Eq + Hash, +{ #[inline] pub fn new(capacity: usize) -> Self { LruCache { @@ -37,7 +44,31 @@ impl LruCache { } } -impl LruCache { +impl PartialEq for LruCache +where + K: Eq + Hash, + V: Eq, + S: BuildHasher + Default, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.capacity() == other.capacity() && self.iter().eq(other) + } +} + +impl Eq for LruCache +where + K: Eq + Hash, + V: Eq, + S: BuildHasher + Default, +{ +} + +impl LruCache +where + K: Eq + Hash, + S: BuildHasher + Default, +{ #[inline] pub fn with_hasher(capacity: usize, hash_builder: S) -> Self { LruCache { @@ -82,9 +113,10 @@ impl LruCache { } } -impl LruCache +impl LruCache where - S: BuildHasher, + K: Eq + Hash, + S: BuildHasher + Default, { #[inline] pub fn contains_key(&mut self, key: &Q) -> bool @@ -232,7 +264,7 @@ where } } -impl Clone for LruCache { +impl Clone for LruCache { #[inline] fn clone(&self) -> Self { LruCache { @@ -242,7 +274,7 @@ impl Clone for LruCache< } } -impl Extend<(K, V)> for LruCache { +impl Extend<(K, V)> for LruCache { #[inline] fn extend>(&mut self, iter: I) { for (k, v) in iter { @@ -251,7 +283,11 @@ impl Extend<(K, V)> for LruCache { } } -impl IntoIterator for LruCache { +impl IntoIterator for LruCache +where + K: Eq + Hash, + S: BuildHasher + Default, +{ type Item = (K, V); type IntoIter = IntoIter; @@ -261,7 +297,11 @@ impl IntoIterator for LruCache { } } -impl<'a, K, V, S> IntoIterator for &'a LruCache { +impl<'a, K, V, S> IntoIterator for &'a LruCache +where + K: Eq + Hash, + S: BuildHasher + Default, +{ type Item = (&'a K, &'a V); type IntoIter = Iter<'a, K, V>; @@ -271,7 +311,11 @@ impl<'a, K, V, S> IntoIterator for &'a LruCache { } } -impl<'a, K, V, S> IntoIterator for &'a mut LruCache { +impl<'a, K, V, S> IntoIterator for &'a mut LruCache +where + K: Eq + Hash, + S: BuildHasher + Default, +{ type Item = (&'a K, &'a mut V); type IntoIter = IterMut<'a, K, V>; @@ -283,8 +327,9 @@ impl<'a, K, V, S> IntoIterator for &'a mut LruCache { impl fmt::Debug for LruCache where - K: fmt::Debug, + K: Eq + Hash + fmt::Debug, V: fmt::Debug, + S: BuildHasher + Default, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_map().entries(self.iter().rev()).finish() diff --git a/src/serde.rs b/src/serde.rs index 57c3b16..dc000ab 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -5,12 +5,12 @@ use core::{ }; use serde::{ - de::{MapAccess, SeqAccess, Visitor}, - ser::{SerializeMap, SerializeSeq}, + de::{self, MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq, SerializeStruct}, Deserialize, Deserializer, Serialize, Serializer, }; -use crate::{LinkedHashMap, LinkedHashSet}; +use crate::{LinkedHashMap, LinkedHashSet, LruCache}; // LinkedHashMap impls @@ -159,3 +159,154 @@ where deserializer.deserialize_seq(LinkedHashSetVisitor::default()) } } + +// LruCache impls + +impl Serialize for LruCache +where + K: Serialize + Eq + Hash, + V: Serialize, + S: BuildHasher + Default, +{ + #[inline] + fn serialize(&self, serializer: T) -> Result { + let mut state = serializer.serialize_struct("LruCache", 2)?; + state.serialize_field("map", &self.map)?; + state.serialize_field("max_size", &self.max_size)?; + state.end() + } +} + +impl<'de, K, V, S> Deserialize<'de> for LruCache +where + K: Deserialize<'de> + Eq + Hash, + V: Deserialize<'de>, + S: BuildHasher + Default, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + enum Field { + Map, + MaxSize, + } + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("`map` or `max_size`") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "map" => Ok(Field::Map), + "max_size" => Ok(Field::MaxSize), + _ => Err(de::Error::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + #[derive(Debug)] + struct LruCacheVisitor + where + K: Eq + Hash, + S: BuildHasher + Default, + { + marker: PhantomData>, + } + + impl LruCacheVisitor + where + K: Eq + Hash, + S: BuildHasher + Default, + { + fn new() -> Self { + LruCacheVisitor { + marker: PhantomData, + } + } + } + + impl Default for LruCacheVisitor + where + K: Eq + Hash, + S: BuildHasher + Default, + { + fn default() -> Self { + Self::new() + } + } + + impl<'de, K, V, S> Visitor<'de> for LruCacheVisitor + where + K: Deserialize<'de> + Eq + Hash, + V: Deserialize<'de>, + S: BuildHasher + Default, + { + type Value = LruCache; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct LruCache") + } + + fn visit_seq(self, mut outseq: M) -> Result, M::Error> + where + M: SeqAccess<'de>, + { + let map = outseq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let max_size = outseq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + Ok(LruCache:: { map, max_size }) + } + + fn visit_map(self, mut outmap: M) -> Result, M::Error> + where + M: MapAccess<'de>, + { + let mut map = None; + let mut max_size = None; + while let Some(key) = outmap.next_key()? { + match key { + Field::Map => { + if map.is_some() { + return Err(de::Error::duplicate_field("map")); + } + map = Some(outmap.next_value()?); + } + Field::MaxSize => { + if max_size.is_some() { + return Err(de::Error::duplicate_field("max_size")); + } + max_size = Some(outmap.next_value()?); + } + } + } + let map = map.ok_or_else(|| de::Error::missing_field("map"))?; + let max_size = max_size.ok_or_else(|| de::Error::missing_field("max_size"))?; + Ok(LruCache:: { map, max_size }) + } + } + + const FIELDS: &'static [&'static str] = &["map", "max_size"]; + deserializer.deserialize_struct("LruCache", FIELDS, LruCacheVisitor::default()) + } +} diff --git a/tests/serde.rs b/tests/serde.rs index 2cf4a3e..6bc5d51 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,10 +1,23 @@ #![cfg(feature = "serde_impl")] -use std::hash::BuildHasherDefault; - -use hashlink::{LinkedHashMap, LinkedHashSet}; +use fxhash::FxBuildHasher; +use hashlink::{LinkedHashMap, LinkedHashSet, LruCache}; use rustc_hash::FxHasher; use serde_test::{assert_tokens, Token}; +use std::hash::BuildHasherDefault; + +#[cfg(target_pointer_width = "64")] +fn token_usize(t: usize) -> Token { + Token::U64(t as u64) +} +#[cfg(target_pointer_width = "32")] +fn token_usize(t: usize) -> Token { + Token::U32(t as u32) +} +#[cfg(target_pointer_width = "16")] +fn token_usize(t: usize) -> Token { + Token::U16(t as u16) +} #[test] fn map_serde_tokens_empty() { @@ -108,3 +121,105 @@ fn set_serde_tokens_generic() { ], ); } + +#[test] +fn lru_serde_tokens_empty() { + let map = LruCache::::new(16); + + assert_tokens( + &map, + &[ + Token::Struct { + name: "LruCache", + len: 2, + }, + Token::Str("map"), + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::Str("max_size"), + token_usize(16), + Token::StructEnd, + ], + ); +} + +#[test] +fn lru_serde_tokens() { + let mut map = LruCache::new(16); + map.insert('a', 10); + map.insert('b', 20); + map.insert('c', 30); + + assert_tokens( + &map, + &[ + Token::Struct { + name: "LruCache", + len: 2, + }, + Token::Str("map"), + Token::Map { len: Some(3) }, + Token::Char('a'), + Token::I32(10), + Token::Char('b'), + Token::I32(20), + Token::Char('c'), + Token::I32(30), + Token::MapEnd, + Token::Str("max_size"), + token_usize(16), + Token::StructEnd, + ], + ); +} + +#[test] +fn lru_serde_tokens_empty_generic() { + let map = LruCache::::with_hasher(16, FxBuildHasher::default()); + + assert_tokens( + &map, + &[ + Token::Struct { + name: "LruCache", + len: 2, + }, + Token::Str("map"), + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::Str("max_size"), + token_usize(16), + Token::StructEnd, + ], + ); +} + +#[test] +fn lru_serde_tokens_generic() { + let mut map = LruCache::with_hasher(16, FxBuildHasher::default()); + map.insert('a', 10); + map.insert('b', 20); + map.insert('c', 30); + + assert_tokens( + &map, + &[ + Token::Struct { + name: "LruCache", + len: 2, + }, + Token::Str("map"), + Token::Map { len: Some(3) }, + Token::Char('a'), + Token::I32(10), + Token::Char('b'), + Token::I32(20), + Token::Char('c'), + Token::I32(30), + Token::MapEnd, + Token::Str("max_size"), + token_usize(16), + Token::StructEnd, + ], + ); +} From 6870e4e97cf889ae24bdafff8bc5af5dff148a5b Mon Sep 17 00:00:00 2001 From: John Smith Date: Sun, 3 Apr 2022 12:57:50 -0400 Subject: [PATCH 2/4] add peek_lru --- src/lru_cache.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lru_cache.rs b/src/lru_cache.rs index e745689..41af9a6 100644 --- a/src/lru_cache.rs +++ b/src/lru_cache.rs @@ -262,6 +262,14 @@ where pub fn remove_lru(&mut self) -> Option<(K, V)> { self.map.pop_front() } + + /// Peek at the least recently used entry and return a reference to it. + /// + /// If the `LruCache` is empty this will return None. + #[inline] + pub fn peek_lru(&mut self) -> Option<(&K, &V)> { + self.map.front() + } } impl Clone for LruCache { From 7d09d96187cb0d89fa4ca91d25a368cedbfca161 Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 3 Mar 2023 17:25:53 -0500 Subject: [PATCH 3/4] add better lru mechanism --- src/lru_cache.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/lru_cache.rs b/src/lru_cache.rs index 41af9a6..a13b658 100644 --- a/src/lru_cache.rs +++ b/src/lru_cache.rs @@ -131,10 +131,12 @@ where /// /// If necessary, will remove the value at the front of the LRU list to make room. #[inline] - pub fn insert(&mut self, k: K, v: V) -> Option { + pub fn insert(&mut self, k: K, v: V, remove_lru_callback: F) -> Option { let old_val = self.map.insert(k, v); if self.len() > self.capacity() { - self.remove_lru(); + if let Some(x) = self.remove_lru() { + remove_lru_callback(x.0, x.1) + } } old_val } @@ -196,9 +198,11 @@ where /// `Entry::to_back` / `Entry::to_front` you can manually control the position of this entry in /// the LRU list. #[inline] - pub fn entry(&mut self, key: K) -> Entry<'_, K, V, S> { + pub fn entry(&mut self, key: K, remove_lru_callback: F) -> Entry<'_, K, V, S> { if self.len() > self.capacity() { - self.remove_lru(); + if let Some(x) = self.remove_lru() { + remove_lru_callback(x.0, x.1) + } } self.map.entry(key) } @@ -218,9 +222,14 @@ where /// calling `Entry::to_back` / `Entry::to_front` you can manually control the position of this /// entry in the LRU list. #[inline] - pub fn raw_entry_mut(&mut self) -> RawEntryBuilderMut<'_, K, V, S> { + pub fn raw_entry_mut( + &mut self, + remove_lru_callback: F, + ) -> RawEntryBuilderMut<'_, K, V, S> { if self.len() > self.capacity() { - self.remove_lru(); + if let Some(x) = self.remove_lru() { + remove_lru_callback(x.0, x.1) + } } self.map.raw_entry_mut() } @@ -248,9 +257,11 @@ where /// If there are more entries in the `LruCache` than the new capacity will allow, they are /// removed. #[inline] - pub fn set_capacity(&mut self, capacity: usize) { + pub fn set_capacity(&mut self, capacity: usize, remove_lru_callback: F) { for _ in capacity..self.len() { - self.remove_lru(); + if let Some(x) = self.remove_lru() { + remove_lru_callback(x.0, x.1) + } } self.max_size = capacity; } @@ -286,7 +297,11 @@ impl Extend<(K, V)> for LruCache>(&mut self, iter: I) { for (k, v) in iter { - self.insert(k, v); + //self.insert(k, v); + self.map.insert(k, v); + if self.len() > self.capacity() { + self.remove_lru(); + } } } } From 14ea55b8b148f51b834c0c7ad5755066400d8c71 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 2 Sep 2023 18:40:29 -0400 Subject: [PATCH 4/4] lru with callback refactor --- src/lru_cache.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++--- tests/serde.rs | 8 +++-- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/lru_cache.rs b/src/lru_cache.rs index a13b658..0c433d7 100644 --- a/src/lru_cache.rs +++ b/src/lru_cache.rs @@ -131,7 +131,25 @@ where /// /// If necessary, will remove the value at the front of the LRU list to make room. #[inline] - pub fn insert(&mut self, k: K, v: V, remove_lru_callback: F) -> Option { + pub fn insert(&mut self, k: K, v: V) -> Option { + let old_val = self.map.insert(k, v); + if self.len() > self.capacity() { + self.remove_lru(); + } + old_val + } + + /// Insert a new value into the `LruCache` with LRU callback. + /// + /// If necessary, will remove the value at the front of the LRU list to make room. + /// Calls a callback if there was an LRU removed value + #[inline] + pub fn insert_with_callback( + &mut self, + k: K, + v: V, + remove_lru_callback: F, + ) -> Option { let old_val = self.map.insert(k, v); if self.len() > self.capacity() { if let Some(x) = self.remove_lru() { @@ -198,7 +216,28 @@ where /// `Entry::to_back` / `Entry::to_front` you can manually control the position of this entry in /// the LRU list. #[inline] - pub fn entry(&mut self, key: K, remove_lru_callback: F) -> Entry<'_, K, V, S> { + pub fn entry(&mut self, key: K) -> Entry<'_, K, V, S> { + if self.len() > self.capacity() { + self.remove_lru(); + } + self.map.entry(key) + } + + /// Like `entry` but with LRU callback. + /// If the returned entry is vacant, it will always have room to insert a single value. By + /// using the entry API, you can exceed the configured capacity by 1. + /// + /// The returned entry is not automatically moved to the back of the LRU list. By calling + /// `Entry::to_back` / `Entry::to_front` you can manually control the position of this entry in + /// the LRU list. + /// Calls a callback if there was an LRU removed value + + #[inline] + pub fn entry_with_callback( + &mut self, + key: K, + remove_lru_callback: F, + ) -> Entry<'_, K, V, S> { if self.len() > self.capacity() { if let Some(x) = self.remove_lru() { remove_lru_callback(x.0, x.1) @@ -221,8 +260,25 @@ where /// The constructed raw entry is never automatically moved to the back of the LRU list. By /// calling `Entry::to_back` / `Entry::to_front` you can manually control the position of this /// entry in the LRU list. + /// Calls a callback if there was an LRU removed value + #[inline] - pub fn raw_entry_mut( + pub fn raw_entry_mut(&mut self) -> RawEntryBuilderMut<'_, K, V, S> { + if self.len() > self.capacity() { + self.remove_lru(); + } + self.map.raw_entry_mut() + } + + /// Like `raw_entry` but with LRU callback. + /// If the constructed raw entry is vacant, it will always have room to insert a single value. + /// By using the raw entry API, you can exceed the configured capacity by 1. + /// + /// The constructed raw entry is never automatically moved to the back of the LRU list. By + /// calling `Entry::to_back` / `Entry::to_front` you can manually control the position of this + /// entry in the LRU list. + #[inline] + pub fn raw_entry_mut_with_callback( &mut self, remove_lru_callback: F, ) -> RawEntryBuilderMut<'_, K, V, S> { @@ -257,7 +313,25 @@ where /// If there are more entries in the `LruCache` than the new capacity will allow, they are /// removed. #[inline] - pub fn set_capacity(&mut self, capacity: usize, remove_lru_callback: F) { + pub fn set_capacity(&mut self, capacity: usize) { + for _ in capacity..self.len() { + self.remove_lru(); + } + self.max_size = capacity; + } + + /// Like `set_capacity` but with LRU callback. + /// Set the new cache capacity for the `LruCache` with an LRU callback. + /// + /// If there are more entries in the `LruCache` than the new capacity will allow, they are + /// removed. + /// Calls a callback if there was an LRU removed value + #[inline] + pub fn set_capacity_with_callback( + &mut self, + capacity: usize, + remove_lru_callback: F, + ) { for _ in capacity..self.len() { if let Some(x) = self.remove_lru() { remove_lru_callback(x.0, x.1) diff --git a/tests/serde.rs b/tests/serde.rs index 6bc5d51..4c6aab0 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,6 +1,5 @@ #![cfg(feature = "serde_impl")] -use fxhash::FxBuildHasher; use hashlink::{LinkedHashMap, LinkedHashSet, LruCache}; use rustc_hash::FxHasher; use serde_test::{assert_tokens, Token}; @@ -175,7 +174,10 @@ fn lru_serde_tokens() { #[test] fn lru_serde_tokens_empty_generic() { - let map = LruCache::::with_hasher(16, FxBuildHasher::default()); + let map = LruCache::>::with_hasher( + 16, + BuildHasherDefault::::default(), + ); assert_tokens( &map, @@ -196,7 +198,7 @@ fn lru_serde_tokens_empty_generic() { #[test] fn lru_serde_tokens_generic() { - let mut map = LruCache::with_hasher(16, FxBuildHasher::default()); + let mut map = LruCache::with_hasher(16, BuildHasherDefault::::default()); map.insert('a', 10); map.insert('b', 20); map.insert('c', 30);