refactor(dragonfly-client-storage): optimize lru_cache for storage (#1040)
Signed-off-by: Gaius <gaius.qi@gmail.com>
This commit is contained in:
parent
9ca89909f3
commit
70eca028cd
|
|
@ -1,11 +1,28 @@
|
||||||
use std::{borrow::Borrow, collections::HashMap, hash::Hash, hash::Hasher, num::NonZeroUsize, ptr};
|
/*
|
||||||
|
* Copyright 2025 The Dragonfly Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{borrow::Borrow, collections::HashMap, hash::Hash, hash::Hasher};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
/// KeyRef is a reference to the key.
|
/// KeyRef is a reference to the key.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct KeyRef<K> {
|
struct KeyRef<K> {
|
||||||
k: *const K,
|
k: *const K,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KeyRef implements Hash for KeyRef.
|
||||||
impl<K: Hash> Hash for KeyRef<K> {
|
impl<K: Hash> Hash for KeyRef<K> {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -15,6 +32,7 @@ impl<K: Hash> Hash for KeyRef<K> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KeyRef implements PartialEq for KeyRef.
|
||||||
impl<K: PartialEq> PartialEq for KeyRef<K> {
|
impl<K: PartialEq> PartialEq for KeyRef<K> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -25,12 +43,14 @@ impl<K: PartialEq> PartialEq for KeyRef<K> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KeyRef implements Eq for KeyRef.
|
||||||
impl<K: Eq> Eq for KeyRef<K> {}
|
impl<K: Eq> Eq for KeyRef<K> {}
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
/// KeyWrapper is a wrapper for the key.
|
/// KeyWrapper is a wrapper for the key.
|
||||||
|
#[repr(transparent)]
|
||||||
struct KeyWrapper<K: ?Sized>(K);
|
struct KeyWrapper<K: ?Sized>(K);
|
||||||
|
|
||||||
|
/// KeyWrapper implements reference conversion.
|
||||||
impl<K: ?Sized> KeyWrapper<K> {
|
impl<K: ?Sized> KeyWrapper<K> {
|
||||||
/// from_ref creates a new KeyWrapper from a reference to the key.
|
/// from_ref creates a new KeyWrapper from a reference to the key.
|
||||||
fn from_ref(key: &K) -> &Self {
|
fn from_ref(key: &K) -> &Self {
|
||||||
|
|
@ -38,12 +58,14 @@ impl<K: ?Sized> KeyWrapper<K> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KeyWrapper implements Hash for KeyWrapper.
|
||||||
impl<K: ?Sized + Hash> Hash for KeyWrapper<K> {
|
impl<K: ?Sized + Hash> Hash for KeyWrapper<K> {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.0.hash(state)
|
self.0.hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KeyWrapper implements PartialEq for KeyWrapper.
|
||||||
impl<K: ?Sized + PartialEq> PartialEq for KeyWrapper<K> {
|
impl<K: ?Sized + PartialEq> PartialEq for KeyWrapper<K> {
|
||||||
#![allow(unknown_lints)]
|
#![allow(unknown_lints)]
|
||||||
#[allow(clippy::unconditional_recursion)]
|
#[allow(clippy::unconditional_recursion)]
|
||||||
|
|
@ -52,13 +74,16 @@ impl<K: ?Sized + PartialEq> PartialEq for KeyWrapper<K> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KeyWrapper implements Eq for KeyWrapper.
|
||||||
impl<K: ?Sized + Eq> Eq for KeyWrapper<K> {}
|
impl<K: ?Sized + Eq> Eq for KeyWrapper<K> {}
|
||||||
|
|
||||||
|
/// KeyWrapper implements Borrow for KeyWrapper.
|
||||||
impl<K, Q> Borrow<KeyWrapper<Q>> for KeyRef<K>
|
impl<K, Q> Borrow<KeyWrapper<Q>> for KeyRef<K>
|
||||||
where
|
where
|
||||||
K: Borrow<Q>,
|
K: Borrow<Q>,
|
||||||
Q: ?Sized,
|
Q: ?Sized,
|
||||||
{
|
{
|
||||||
|
/// borrow borrows the key.
|
||||||
fn borrow(&self) -> &KeyWrapper<Q> {
|
fn borrow(&self) -> &KeyWrapper<Q> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let key = &*self.k;
|
let key = &*self.k;
|
||||||
|
|
@ -67,6 +92,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Entry is a cache entry.
|
||||||
struct Entry<K, V> {
|
struct Entry<K, V> {
|
||||||
key: K,
|
key: K,
|
||||||
value: V,
|
value: V,
|
||||||
|
|
@ -74,7 +100,9 @@ struct Entry<K, V> {
|
||||||
next: Option<*mut Entry<K, V>>,
|
next: Option<*mut Entry<K, V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Entry implements Drop for Entry.
|
||||||
impl<K, V> Entry<K, V> {
|
impl<K, V> Entry<K, V> {
|
||||||
|
/// new creates a new Entry.
|
||||||
fn new(key: K, value: V) -> Self {
|
fn new(key: K, value: V) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key,
|
key,
|
||||||
|
|
@ -85,25 +113,28 @@ impl<K, V> Entry<K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// LruCache is a least recently used cache.
|
||||||
pub struct LruCache<K, V> {
|
pub struct LruCache<K, V> {
|
||||||
cap: NonZeroUsize,
|
capacity: usize,
|
||||||
map: HashMap<KeyRef<K>, Box<Entry<K, V>>>,
|
map: HashMap<KeyRef<K>, Box<Entry<K, V>>>,
|
||||||
head: Option<*mut Entry<K, V>>,
|
head: Option<*mut Entry<K, V>>,
|
||||||
tail: Option<*mut Entry<K, V>>,
|
tail: Option<*mut Entry<K, V>>,
|
||||||
_marker: std::marker::PhantomData<K>,
|
_marker: std::marker::PhantomData<K>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// LruCache implements LruCache.
|
||||||
impl<K: Hash + Eq, V> LruCache<K, V> {
|
impl<K: Hash + Eq, V> LruCache<K, V> {
|
||||||
/// new creates a new LruCache.
|
/// new creates a new LruCache.
|
||||||
pub fn new(cap: NonZeroUsize) -> Self {
|
pub fn new(capacity: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cap,
|
capacity,
|
||||||
map: HashMap::with_capacity(cap.get()),
|
map: HashMap::with_capacity(capacity),
|
||||||
head: None,
|
head: None,
|
||||||
tail: None,
|
tail: None,
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get gets the value of the key.
|
/// get gets the value of the key.
|
||||||
pub fn get<'a, Q>(&'a mut self, k: &Q) -> Option<&'a V>
|
pub fn get<'a, Q>(&'a mut self, k: &Q) -> Option<&'a V>
|
||||||
where
|
where
|
||||||
|
|
@ -113,11 +144,8 @@ impl<K: Hash + Eq, V> LruCache<K, V> {
|
||||||
if let Some(entry) = self.map.get_mut(KeyWrapper::from_ref(k)) {
|
if let Some(entry) = self.map.get_mut(KeyWrapper::from_ref(k)) {
|
||||||
let entry_ptr: *mut Entry<K, V> = &mut **entry;
|
let entry_ptr: *mut Entry<K, V> = &mut **entry;
|
||||||
|
|
||||||
unsafe {
|
self.detach(entry_ptr);
|
||||||
self.detach(entry_ptr);
|
self.attach(entry_ptr);
|
||||||
self.attach(entry_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(&unsafe { &*entry_ptr }.value)
|
Some(&unsafe { &*entry_ptr }.value)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -125,35 +153,24 @@ impl<K: Hash + Eq, V> LruCache<K, V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// put puts the key and value into the cache.
|
/// put puts the key and value into the cache.
|
||||||
pub fn put(&mut self, key: K, value: V) -> Option<V> {
|
pub fn put(&mut self, key: K, mut value: V) -> Option<V> {
|
||||||
let key_ref = KeyRef { k: &key };
|
if let Some(existing_entry) = self.map.get_mut(&KeyRef { k: &key }) {
|
||||||
|
let entry = existing_entry.as_mut();
|
||||||
|
std::mem::swap(&mut entry.value, &mut value);
|
||||||
|
|
||||||
if let Some(existing_entry) = self.map.get_mut(&key_ref) {
|
let entry_ptr: *mut Entry<K, V> = entry;
|
||||||
let entry_ptr = existing_entry.as_mut() as *mut Entry<K, V>;
|
self.detach(entry_ptr);
|
||||||
|
self.attach(entry_ptr);
|
||||||
let old_value = unsafe {
|
return Some(value);
|
||||||
let old_value = ptr::read(&(*entry_ptr).value);
|
|
||||||
ptr::write(&mut (*entry_ptr).value, value);
|
|
||||||
old_value
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
self.detach(entry_ptr);
|
|
||||||
self.attach(entry_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(old_value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut evicted_value = None;
|
let mut evicted_value = None;
|
||||||
if self.map.len() >= self.cap.get() {
|
if self.map.len() >= self.capacity {
|
||||||
if let Some(tail) = self.tail {
|
if let Some(tail) = self.tail {
|
||||||
|
self.detach(tail);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let tail_key_ref = KeyRef { k: &(*tail).key };
|
if let Some(entry) = self.map.remove(&KeyRef { k: &(*tail).key }) {
|
||||||
|
|
||||||
self.detach(tail);
|
|
||||||
|
|
||||||
if let Some(entry) = self.map.remove(&tail_key_ref) {
|
|
||||||
evicted_value = Some(entry.value);
|
evicted_value = Some(entry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +178,7 @@ impl<K: Hash + Eq, V> LruCache<K, V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_entry = Box::new(Entry::new(key, value));
|
let new_entry = Box::new(Entry::new(key, value));
|
||||||
let key_ptr = &new_entry.key as *const K;
|
let key_ptr: *const K = &new_entry.key;
|
||||||
let entry_ptr = Box::into_raw(new_entry);
|
let entry_ptr = Box::into_raw(new_entry);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -174,30 +191,35 @@ impl<K: Hash + Eq, V> LruCache<K, V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// detach detaches the entry from the cache.
|
/// detach detaches the entry from the cache.
|
||||||
unsafe fn detach(&mut self, entry: *mut Entry<K, V>) {
|
fn detach(&mut self, entry: *mut Entry<K, V>) {
|
||||||
let prev = (*entry).prev;
|
unsafe {
|
||||||
let next = (*entry).next;
|
let prev = (*entry).prev;
|
||||||
|
let next = (*entry).next;
|
||||||
|
|
||||||
match prev {
|
match prev {
|
||||||
Some(prev) => (*prev).next = next,
|
Some(prev) => (*prev).next = next,
|
||||||
None => self.head = next,
|
None => self.head = next,
|
||||||
|
}
|
||||||
|
|
||||||
|
match next {
|
||||||
|
Some(next) => (*next).prev = prev,
|
||||||
|
None => self.tail = prev,
|
||||||
|
}
|
||||||
|
|
||||||
|
(*entry).prev = None;
|
||||||
|
(*entry).next = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match next {
|
|
||||||
Some(next) => (*next).prev = prev,
|
|
||||||
None => self.tail = prev,
|
|
||||||
}
|
|
||||||
|
|
||||||
(*entry).prev = None;
|
|
||||||
(*entry).next = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// attach attaches the entry to the cache.
|
/// attach attaches the entry to the cache.
|
||||||
unsafe fn attach(&mut self, entry: *mut Entry<K, V>) {
|
fn attach(&mut self, entry: *mut Entry<K, V>) {
|
||||||
match self.head {
|
match self.head {
|
||||||
Some(old_head) => {
|
Some(head) => {
|
||||||
(*entry).next = Some(old_head);
|
unsafe {
|
||||||
(*old_head).prev = Some(entry);
|
(*entry).next = Some(head);
|
||||||
|
(*head).prev = Some(entry);
|
||||||
|
}
|
||||||
|
|
||||||
self.head = Some(entry);
|
self.head = Some(entry);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -234,13 +256,11 @@ impl<K: Hash + Eq, V> LruCache<K, V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let tail = self.tail?;
|
let tail = self.tail?;
|
||||||
|
self.detach(tail);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.detach(tail);
|
|
||||||
|
|
||||||
let tail_key_ref = KeyRef { k: &(*tail).key };
|
|
||||||
self.map
|
self.map
|
||||||
.remove(&tail_key_ref)
|
.remove(&KeyRef { k: &(*tail).key })
|
||||||
.map(|entry| (entry.key, entry.value))
|
.map(|entry| (entry.key, entry.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +289,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_put() {
|
fn test_put() {
|
||||||
let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap());
|
let mut cache = LruCache::new(3);
|
||||||
|
|
||||||
assert_eq!(cache.put("key1".to_string(), "value1".to_string()), None);
|
assert_eq!(cache.put("key1".to_string(), "value1".to_string()), None);
|
||||||
assert_eq!(cache.put("key2".to_string(), "value2".to_string()), None);
|
assert_eq!(cache.put("key2".to_string(), "value2".to_string()), None);
|
||||||
|
|
@ -296,7 +316,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get() {
|
fn test_get() {
|
||||||
let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap());
|
let mut cache = LruCache::new(3);
|
||||||
|
|
||||||
cache.put("key1".to_string(), "value1".to_string());
|
cache.put("key1".to_string(), "value1".to_string());
|
||||||
cache.put("key2".to_string(), "value2".to_string());
|
cache.put("key2".to_string(), "value2".to_string());
|
||||||
|
|
@ -318,7 +338,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_peek() {
|
fn test_peek() {
|
||||||
let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap());
|
let mut cache = LruCache::new(3);
|
||||||
|
|
||||||
cache.put("key1".to_string(), "value1".to_string());
|
cache.put("key1".to_string(), "value1".to_string());
|
||||||
cache.put("key2".to_string(), "value2".to_string());
|
cache.put("key2".to_string(), "value2".to_string());
|
||||||
|
|
@ -337,7 +357,7 @@ mod tests {
|
||||||
assert_eq!(cache.peek("key3"), Some(&"value3".to_string()));
|
assert_eq!(cache.peek("key3"), Some(&"value3".to_string()));
|
||||||
assert_eq!(cache.peek("key4"), Some(&"value4".to_string()));
|
assert_eq!(cache.peek("key4"), Some(&"value4".to_string()));
|
||||||
|
|
||||||
let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
|
let mut cache = LruCache::new(2);
|
||||||
cache.put("key1".to_string(), "value1".to_string());
|
cache.put("key1".to_string(), "value1".to_string());
|
||||||
cache.put("key2".to_string(), "value2".to_string());
|
cache.put("key2".to_string(), "value2".to_string());
|
||||||
|
|
||||||
|
|
@ -345,7 +365,7 @@ mod tests {
|
||||||
cache.put("key3".to_string(), "value3".to_string());
|
cache.put("key3".to_string(), "value3".to_string());
|
||||||
assert_eq!(cache.peek("key1"), None);
|
assert_eq!(cache.peek("key1"), None);
|
||||||
|
|
||||||
let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
|
let mut cache = LruCache::new(2);
|
||||||
cache.put("key1".to_string(), "value1".to_string());
|
cache.put("key1".to_string(), "value1".to_string());
|
||||||
cache.put("key2".to_string(), "value2".to_string());
|
cache.put("key2".to_string(), "value2".to_string());
|
||||||
|
|
||||||
|
|
@ -357,7 +377,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contains() {
|
fn test_contains() {
|
||||||
let mut cache = LruCache::new(NonZeroUsize::new(5).unwrap());
|
let mut cache = LruCache::new(5);
|
||||||
|
|
||||||
let test_cases = vec![
|
let test_cases = vec![
|
||||||
("piece_1", Bytes::from("data 1"), false),
|
("piece_1", Bytes::from("data 1"), false),
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ impl Cache {
|
||||||
/// new creates a new cache with the specified capacity.
|
/// new creates a new cache with the specified capacity.
|
||||||
pub fn new(capacity: usize, tasks_capacity: usize) -> Result<Self> {
|
pub fn new(capacity: usize, tasks_capacity: usize) -> Result<Self> {
|
||||||
let capacity = NonZeroUsize::new(capacity).ok_or(Error::InvalidParameter)?;
|
let capacity = NonZeroUsize::new(capacity).ok_or(Error::InvalidParameter)?;
|
||||||
let tasks_capacity = NonZeroUsize::new(tasks_capacity).ok_or(Error::InvalidParameter)?;
|
|
||||||
let tasks = Arc::new(RwLock::new(LruCache::new(tasks_capacity)));
|
let tasks = Arc::new(RwLock::new(LruCache::new(tasks_capacity)));
|
||||||
|
|
||||||
Ok(Cache {
|
Ok(Cache {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue