From fa67f522267603c4ee200e8a23c193c749c5b19b Mon Sep 17 00:00:00 2001 From: ticbh Date: Sun, 23 Jun 2024 20:08:50 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=B8=A6=E6=9C=89ttl?= =?UTF-8?q?=E7=9A=84Lru?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 5 +- Cargo.toml | 3 +- examples/lru.rs | 11 ++ src/cache/lru.rs | 278 +++++++++++++++++++++++++++++++++++++----- src/cache/lruk.rs | 11 +- src/lib.rs | 2 + src/util.rs | 12 ++ 7 files changed, 288 insertions(+), 34 deletions(-) create mode 100644 src/util.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 0db5873..518352f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "cmake.configureOnOpen": true + "cmake.configureOnOpen": true, + "rust-analyzer.cargo.features": [ + "ttl" + ], } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d39f025..6eae234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,5 @@ opt-level = 3 debug = true [features] -hashbrown=[] \ No newline at end of file +hashbrown=[] +ttl=[] \ No newline at end of file diff --git a/examples/lru.rs b/examples/lru.rs index 765ea62..4d60e80 100644 --- a/examples/lru.rs +++ b/examples/lru.rs @@ -1,5 +1,14 @@ use algorithm::LruCache; + +fn run_ttl() { + let mut lru = LruCache::new(3); + lru.insert_with_ttl("help", "ok", 1); + assert_eq!(lru.len(), 1); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("help"), None); + assert_eq!(lru.len(), 0); +} fn main() { let mut lru = LruCache::new(3); lru.insert("now", "ok"); @@ -10,4 +19,6 @@ fn main() { assert_eq!(lru.get("hello"), Some(&"algorithm")); assert_eq!(lru.get("this"), Some(&"lru")); assert_eq!(lru.get("now"), None); + + run_ttl(); } diff --git a/src/cache/lru.rs b/src/cache/lru.rs index 7366587..98ba69c 100644 --- a/src/cache/lru.rs +++ b/src/cache/lru.rs @@ -17,14 +17,21 @@ use std::{ use crate::{HashMap, DefaultHasher}; use super::{KeyRef, KeyWrapper}; +#[cfg(feature = "ttl")] +use crate::{get_timestamp}; +#[cfg(feature = "ttl")] +const DEFAULT_CHECK_STEP: u64 = 120; /// Lru节点数据 -struct LruEntry { +pub(crate) struct LruEntry { /// 头部节点及尾部结点均未初始化值 pub key: mem::MaybeUninit, /// 头部节点及尾部结点均未初始化值 pub val: mem::MaybeUninit, pub prev: *mut LruEntry, pub next: *mut LruEntry, + + #[cfg(feature = "ttl")] + pub expire: u64, } impl LruEntry { @@ -34,6 +41,8 @@ impl LruEntry { val: mem::MaybeUninit::uninit(), prev: ptr::null_mut(), next: ptr::null_mut(), + #[cfg(feature = "ttl")] + expire: u64::MAX, } } @@ -43,8 +52,36 @@ impl LruEntry { val: mem::MaybeUninit::new(v), prev: ptr::null_mut(), next: ptr::null_mut(), + #[cfg(feature = "ttl")] + expire: u64::MAX, } } + + #[cfg(feature = "ttl")] + #[allow(dead_code)] + pub fn new_expire(k: K, v: V, expire: u64) -> Self { + LruEntry { + key: mem::MaybeUninit::new(k), + val: mem::MaybeUninit::new(v), + prev: ptr::null_mut(), + next: ptr::null_mut(), + expire, + } + } + + + #[cfg(feature = "ttl")] + #[inline(always)] + pub fn is_expire(&self) -> bool { + get_timestamp() >= self.expire + } + + + #[cfg(feature = "ttl")] + #[inline(always)] + pub fn is_little(&self, time: &u64) -> bool { + time >= &self.expire + } } @@ -78,6 +115,12 @@ pub struct LruCache { head: *mut LruEntry, /// 双向列表的尾 tail: *mut LruEntry, + + #[cfg(feature = "ttl")] + check_next: u64, + + #[cfg(feature = "ttl")] + check_step: u64, } impl Default for LruCache { @@ -108,9 +151,28 @@ impl LruCache { cap, head, tail, + #[cfg(feature = "ttl")] + check_step: DEFAULT_CHECK_STEP, + #[cfg(feature = "ttl")] + check_next: get_timestamp()+DEFAULT_CHECK_STEP, } } + /// 获取当前检查lru的间隔 + pub fn get_check_step(&self) -> u64 { + self.check_step + } + + /// 设置当前检查lru的间隔 + /// 单位为秒,意思就是每隔多少秒会清理一次数据 + /// 如果数据太大的话遍历一次可能会比较久的时长 + /// 一次清理时间复杂度O(n) + /// 仅仅在插入时触发检查,获取时仅检查当前元素 + pub fn set_check_step(&mut self, check_step: u64) { + self.check_step = check_step; + self.check_next = get_timestamp() + self.check_step; + } + /// 获取当前容量 pub fn capacity(&self) -> usize { self.cap @@ -211,7 +273,7 @@ impl LruCache { /// } /// assert!(lru.len() == 2); /// assert!(lru.get(&"this") == Some(&"lru ok".to_string())); - /// assert!(lru.get(&"hello") == Some(&"algorithm ok".to_string())); + /// assert!(lru.get(&"hello") == Some(&"algorithm ok".to_string())); /// } /// ``` pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { @@ -494,15 +556,7 @@ impl LruCache { K: Borrow, Q: Hash + Eq + ?Sized, { - match self.map.get(KeyWrapper::from_ref(k)) { - Some(l) => { - let node = l.as_ptr(); - self.detach(node); - self.attach(node); - unsafe { Some((&*(*node).key.as_ptr(), &*(*node).val.as_ptr())) } - } - None => None, - } + self.get_mut_key_value(k).map(|(k, v)| (k, &*v)) } /// 获取key值相对应的value值, 根据hash判定, 可编辑被改变 @@ -522,20 +576,24 @@ impl LruCache { K: Borrow, Q: Hash + Eq + ?Sized, { - match self.map.get(KeyWrapper::from_ref(k)) { - Some(l) => { - let node = l.as_ptr(); + self.get_mut_key_value(k).map(|(_, v)| v) + } - self.detach(node); - self.attach(node); - unsafe { Some(&mut *(*node).val.as_mut_ptr()) } + + pub fn get_mut_key_value(&mut self, k: &Q) -> Option<(&K, &mut V)> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + match self.get_node(k) { + Some(node) => { + unsafe { Some(( &*(*node).key.as_mut_ptr(), &mut *(*node).val.as_mut_ptr())) } } None => None, } } - - pub fn get_mut_key_value(&mut self, k: &Q) -> Option<(&K, &mut V)> + pub(crate) fn get_node(&mut self, k: &Q) -> Option<*mut LruEntry> where K: Borrow, Q: Hash + Eq + ?Sized, @@ -544,8 +602,17 @@ impl LruCache { Some(l) => { let node = l.as_ptr(); self.detach(node); + #[cfg(feature = "ttl")] + unsafe { + if (*node).is_expire() { + self.map.remove(KeyWrapper::from_ref(k)); + let _ = *Box::from_raw(node); + return None; + } + } + self.attach(node); - unsafe { Some(( &*(*node).key.as_mut_ptr(), &mut *(*node).val.as_mut_ptr())) } + Some(node) } None => None, } @@ -561,11 +628,35 @@ impl LruCache { /// assert!(lru.insert("this", "lru good") == Some(&"lru")); /// } /// ``` + #[inline(always)] pub fn insert(&mut self, k: K, v: V) -> Option { self.capture_insert(k, v).map(|(_, v, _)| v) } - pub fn capture_insert(&mut self, k: K, mut v: V) -> Option<(K, V, bool)> { + /// 插入带有生存时间的元素 + /// 每次获取像redis一样,并不会更新生存时间 + /// 如果需要更新则需要手动的进行重新设置 + #[inline(always)] + pub fn insert_with_ttl(&mut self, k: K, v: V, ttl: u64) -> Option { + self._capture_insert_with_ttl(k, v, ttl).map(|(_, v, _)| v) + } + + #[inline(always)] + pub fn capture_insert(&mut self, k: K, v: V) -> Option<(K, V, bool)> { + self._capture_insert_with_ttl(k, v, u64::MAX) + } + + #[cfg(feature = "ttl")] + #[inline(always)] + pub fn capture_insert_with_ttl(&mut self, k: K, v: V) -> Option<(K, V, bool)> { + self._capture_insert_with_ttl(k, v, u64::MAX) + } + + #[allow(unused_variables)] + fn _capture_insert_with_ttl(&mut self, k: K, mut v: V, ttl: u64) -> Option<(K, V, bool)> { + #[cfg(feature="ttl")] + self.clear_expire(); + let key = KeyRef::new(&k); match self.map.get_mut(&key) { Some(entry) => { @@ -573,6 +664,10 @@ impl LruCache { unsafe { mem::swap(&mut *(*entry_ptr).val.as_mut_ptr(), &mut v); } + #[cfg(feature="ttl")] + unsafe { + (*entry_ptr).expire = ttl.saturating_add(get_timestamp()); + } self.detach(entry_ptr); self.attach(entry_ptr); @@ -582,6 +677,10 @@ impl LruCache { let (val, entry) = self.replace_or_create_node(k, v); let entry_ptr = entry.as_ptr(); self.attach(entry_ptr); + #[cfg(feature="ttl")] + unsafe { + (*entry_ptr).expire = ttl.saturating_add(get_timestamp()); + } unsafe { self.map .insert(KeyRef::new((*entry_ptr).key.as_ptr()), entry); @@ -591,22 +690,19 @@ impl LruCache { } } - pub fn get_or_insert(&mut self, k: K, f: F) -> &V where F: FnOnce() -> V, { &*self.get_or_insert_mut(k, f) } - - pub fn get_or_insert_mut(&mut self, k: K, f: F) -> &mut V + pub fn get_or_insert_mut<'a, F>(&'a mut self, k: K, f: F) -> &mut V where F: FnOnce() -> V, { - if let Some(l) = self.map.get(KeyWrapper::from_ref(&k)) { - let node = l.as_ptr(); - self.detach(node); - self.attach(node); - unsafe { &mut *(*node).val.as_mut_ptr() } + if let Some(v) = self.get_node(&k) { + return unsafe { + &mut *(*v).val.as_mut_ptr() + }; } else { let v = f(); @@ -621,6 +717,68 @@ impl LruCache { } } + #[cfg(feature="ttl")] + pub fn clear_expire(&mut self) { + let now = get_timestamp(); + if now < self.check_next { + return; + } + self.check_next = now + self.check_step; + unsafe { + let mut ptr = self.tail; + while ptr != self.head { + if (*ptr).is_little(&now) { + let next = (*ptr).prev; + self.detach(ptr); + self.map.remove(&KeyRef::new(&*(*ptr).key.as_ptr())); + let _ = *Box::from_raw(ptr); + ptr = next; + } else { + ptr = (*ptr).prev; + } + } + } + } + + #[cfg(feature="ttl")] + #[inline(always)] + pub fn del_ttl(&mut self, k: &Q) + where + K: Borrow, + Q: Hash + Eq + ?Sized, { + self.set_ttl(k, u64::MAX); + } + + #[cfg(feature="ttl")] + pub fn set_ttl(&mut self, k: &Q, expire: u64) + where + K: Borrow, + Q: Hash + Eq + ?Sized, { + if let Some(v) = self.get_node(&k) { + unsafe { + (*v).expire = get_timestamp().saturating_add(expire); + } + } + } + + #[cfg(feature="ttl")] + pub fn get_ttl(&mut self, k: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, { + if let Some(v) = self.get_node(&k) { + unsafe { + if (*v).expire == u64::MAX { + Some((*v).expire) + } else { + Some((*v).expire.saturating_sub(get_timestamp())) + } + } + } else { + None + } + } + /// 移除元素 /// /// ``` @@ -1420,4 +1578,66 @@ mod tests { assert!(handle.join().is_ok()); } + + #[test] + #[cfg(feature="ttl")] + fn test_ttl_cache() { + let mut lru = LruCache::new(3); + lru.insert_with_ttl("help", "ok", 1); + lru.insert_with_ttl("author", "tickbh", 2); + assert_eq!(lru.len(), 2); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("help"), None); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("author"), None); + assert_eq!(lru.len(), 0); + } + + #[test] + #[cfg(feature="ttl")] + fn test_ttl_check_cache() { + let mut lru = LruCache::new(3); + lru.set_check_step(1); + lru.insert_with_ttl("help", "ok", 1); + lru.insert("now", "algorithm"); + assert_eq!(lru.len(), 2); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.len(), 2); + lru.insert_with_ttl("author", "tickbh", 3); + assert_eq!(lru.len(), 2); + assert_eq!(lru.get("help"), None); + assert_eq!(lru.len(), 2); + } + + #[test] + #[cfg(feature="ttl")] + fn test_ttl_del() { + let mut lru = LruCache::new(3); + lru.insert_with_ttl("help", "ok", 1); + lru.insert_with_ttl("author", "tickbh", 2); + assert_eq!(lru.len(), 2); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("help"), None); + lru.del_ttl(&"author"); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("author"), Some(&"tickbh")); + assert_eq!(lru.len(), 1); + } + + #[test] + #[cfg(feature="ttl")] + fn test_ttl_set() { + let mut lru = LruCache::new(3); + lru.insert_with_ttl("help", "ok", 1); + lru.insert_with_ttl("author", "tickbh", 2); + lru.set_ttl(&"help", 3); + assert_eq!(lru.len(), 2); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("help"), Some(&"ok")); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("author"), None); + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(lru.get("help"), None); + assert_eq!(lru.len(), 0); + } } \ No newline at end of file diff --git a/src/cache/lruk.rs b/src/cache/lruk.rs index db00155..4385a3f 100644 --- a/src/cache/lruk.rs +++ b/src/cache/lruk.rs @@ -20,8 +20,9 @@ use crate::{HashMap, DefaultHasher}; use super::{KeyRef, KeyWrapper}; -const DEFAULT_TIMESK: usize = 10; +const DEFAULT_TIMESK: usize = 2; +/// LruK节点数据 struct LruKEntry { pub key: mem::MaybeUninit, pub val: mem::MaybeUninit, @@ -80,11 +81,15 @@ impl LruKEntry { pub struct LruKCache { map: HashMap, NonNull>, S>, cap: usize, + /// 触发K次数,默认为2 times: usize, + /// K次的队列 head_times: *mut LruKEntry, tail_times: *mut LruKEntry, + /// 普通队列 head: *mut LruKEntry, tail: *mut LruKEntry, + /// 普通队列的长度 lru_count: usize, } @@ -191,7 +196,7 @@ impl LruKCache { /// 加到队列中 fn attach(&mut self, entry: *mut LruKEntry) { unsafe { - (*entry).times += 1; + (*entry).times = (*entry).times.saturating_add(1); if (*entry).times < self.times { self.lru_count += 1; (*entry).next = (*self.head).next; @@ -254,7 +259,7 @@ impl LruKCache { /// } /// assert!(lru.len() == 2); /// assert!(lru.get(&"this") == Some(&"lru ok".to_string())); - /// assert!(lru.get(&"hello") == Some(&"algorithm ok".to_string())); + /// assert!(lru.get(&"hello") == Some(&"algorithm ok".to_string())); /// } /// ``` pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { diff --git a/src/lib.rs b/src/lib.rs index 0449974..c807771 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod quadsort; pub use quadsort::{quad_sort, quad_sort_order_by}; +mod util; mod cache; mod tree; mod map; @@ -12,6 +13,7 @@ pub use tree::RBTree; pub use map::{BitMap, RoaringBitMap}; pub use timer::{TimerWheel, Timer}; pub use arr::{CircularBuffer, FixedVec}; +pub use util::*; #[cfg(feature = "hashbrown")] extern crate hashbrown; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..371689a --- /dev/null +++ b/src/util.rs @@ -0,0 +1,12 @@ + +use std::time::SystemTime; + +#[inline(always)] +pub fn get_timestamp() -> u64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("ok").as_secs() +} + +#[inline(always)] +pub fn get_milltimestamp() -> u128 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("ok").as_millis() +} \ No newline at end of file