- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
LRU(Least Recently Used)是一种常用的页面置换算法,其核心思想是选择最近最久未使用的页面予以淘汰.
基本思想:LRU算法基于一个假设,即如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很低。因此,当缓存空间不足时,算法会选择最久未使用的数据进行淘汰.
实现方式:LRU算法通常通过维护一个队列或链表来实现。当访问一个页面时,如果该页面已经在队列中,则将其移动到队列的头部(最近使用);如果该页面不在队列中,则将其添加到队列的头部,并检查队列长度是否超过预设的阈值。如果队列长度超过阈值,则淘汰队列尾部的页面(最久未使用).
在Lru的结构中,我们要避免key或者val的拷贝。 因为key此时需要在双向列表中保存也需要在HashMap中保存,所以我们要以下方案:
Rc<K>引用计数
通过引用计数来控制生命周期 优点:不用处理不安全的代码 缺点:因为Val可能在遍历中被更改,所以不能存储在双向列表里,取得值的时候需要进行一次Hash 。
*mut K 裸指针
通过unsafe编码来实现 优点:在双向列表及HashMap中均存储一份数值,遍历或者根据key取值均只需一次操作 缺点:需要引入ptr,即用指针的方式来进行生命周期管理 。
此时我们用的是裸指针的方式,让我们先来定义节点数据,数据将存储在该节点里面,key及val的生命周期随节点管理,在删除的时候需同时在列表及在HashMap中进行删除 。
/// Lru节点数据
struct LruEntry<K, V> {
/// 头部节点及尾部结点均未初始化值
pub key: mem::MaybeUninit<K>,
/// 头部节点及尾部结点均未初始化值
pub val: mem::MaybeUninit<V>,
pub prev: *mut LruEntry<K, V>,
pub next: *mut LruEntry<K, V>,
}
接下来需要设计LruCache结构,将由一个map存储数据结构,一个双向链表存储访问优先级,cap表示缓存的容量.
pub struct LruCache<K, V, S> {
/// 存储数据结构
map: HashMap<KeyRef<K>, NonNull<LruEntry<K, V>>, S>,
/// 缓存的总容量
cap: usize,
/// 双向列表的头
head: *mut LruEntry<K, V>,
/// 双向列表的尾
tail: *mut LruEntry<K, V>,
}
其中KeyRef仅仅只是表示裸指针的一层包装,方便重新实现Hash,Eq等trait 。
#[derive(Clone)]
struct KeyRef<K> {
pub k: *const K,
}
首先初始化对象,初始化map及空的双向链表:
impl<K, V, S> LruCache<K, V, S> {
/// 提供hash函数
pub fn with_hasher(cap: usize, hash_builder: S) -> LruCache<K, V, S> {
let cap = cap.max(1);
let map = HashMap::with_capacity_and_hasher(cap, hash_builder);
let head = Box::into_raw(Box::new(LruEntry::new_empty()));
let tail = Box::into_raw(Box::new(LruEntry::new_empty()));
unsafe {
(*head).next = tail;
(*tail).prev = head;
}
Self {
map,
cap,
head,
tail,
}
}
}
插入对象,分已在缓存内和不在缓存内:
pub fn capture_insert(&mut self, k: K, mut v: V) -> Option<(K, V)> {
let key = KeyRef::new(&k);
match self.map.get_mut(&key) {
Some(entry) => {
let entry_ptr = entry.as_ptr();
unsafe {
mem::swap(&mut *(*entry_ptr).val.as_mut_ptr(), &mut v);
}
self.detach(entry_ptr);
self.attach(entry_ptr);
Some((k, v))
}
None => {
let (_, entry) = self.replace_or_create_node(k, v);
let entry_ptr = entry.as_ptr();
self.attach(entry_ptr);
unsafe {
self.map
.insert(KeyRef::new((*entry_ptr).key.as_ptr()), entry);
}
None
}
}
}
存在该元素时,将进行替换 。
unsafe {
mem::swap(&mut *(*entry_ptr).val.as_mut_ptr(), &mut v);
}
并且重新维护访问队列,需要detach然后重新attach使其在队列的最前面,保证最近访问最晚淘汰,从而实现Lru。 如果元素不存在,那么将判断是否缓存队列为满,如果满了将要淘汰的数据进行替换,如果未满创建新的元素,即replace_or_create_node.
在将元素删除时,需要维护好我们的队列,防止出现队列错误及野指针等 。
pub fn remove<Q>(&mut self, k: &Q) -> Option<(K, V)>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
match self.map.remove(KeyWrapper::from_ref(k)) {
Some(l) => unsafe {
self.detach(l.as_ptr());
let node = *Box::from_raw(l.as_ptr());
Some((node.key.assume_init(), node.val.assume_init()))
},
None => None,
}
}
这里因为移除时,仅仅需要一个可以转化成K的引用即可以,并不需要严格的K,利于编程。例如
let mut map = LruCache::new(2);
map.insert("aaaa".to_string(), "bbb");
map.remove("aaaa");
assert!(map.len() == 0);
在此处我们就不需要严格的构建String对象。由于map中的key我们用的是KeyRef,在这里,我们构建一个KeyWrapper进行转化.
// 确保新类型与其内部类型的内存布局完全相同
#[repr(transparent)]
struct KeyWrapper<Q: ?Sized>(Q);
impl<K, Q> Borrow<KeyWrapper<Q>> for KeyRef<K>
where
K: Borrow<Q>,
Q: ?Sized,
{
fn borrow(&self) -> &KeyWrapper<Q> {
let key = unsafe { &*self.k }.borrow();
KeyWrapper::from_ref(key)
}
}
如果移除成功,那么将从双向链表中同步移除,并且将指针中的值重新变成Rust中的对象,以便可以及时被drop,避免内存泄漏.
self.detach(l.as_ptr());
let node = *Box::from_raw(l.as_ptr());
Some((node.key.assume_init(), node.val.assume_init()))
pop
移除栈顶上的数据,最近使用的pop_last
移除栈尾上的数据,最久未被使用的contains_key
判断是否包含key值raw_get
直接获取key的值,不会触发双向链表的维护get
获取key的值,并维护双向链表get_mut
获取key的值,并可以根据需要改变val的值retain
根据函数保留符合条件的元素在cargo.toml中添加 。
[dependencies]
algorithm = "0.1"
use algorithm::LruCache;
fn main() {
let mut lru = LruCache::new(3);
lru.insert("now", "ok");
lru.insert("hello", "algorithm");
lru.insert("this", "lru");
lru.insert("auth", "tickbh");
assert!(lru.len() == 3);
assert_eq!(lru.get("hello"), Some(&"algorithm"));
assert_eq!(lru.get("this"), Some(&"lru"));
assert_eq!(lru.get("now"), None);
}
https://github.com/tickbh/algorithm-rs 。
Lru在缓存场景中也是极其重要的一环,但是普通的Lru容易将热点数据进行移除,如果短时间内大量的数据进入则会将需要缓存的数据全部清空,后续将介绍改进算法Lru-k及Lfu算法.
最后此篇关于Lru在Rust中的实现,源码解析的文章就讲到这里了,如果你想了解更多关于Lru在Rust中的实现,源码解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
背景: 我最近一直在使用 JPA,我为相当大的关系数据库项目生成持久层的轻松程度给我留下了深刻的印象。 我们公司使用大量非 SQL 数据库,特别是面向列的数据库。我对可能对这些数据库使用 JPA 有一
我已经在我的 maven pom 中添加了这些构建配置,因为我希望将 Apache Solr 依赖项与 Jar 捆绑在一起。否则我得到了 SolarServerException: ClassNotF
interface ITurtle { void Fight(); void EatPizza(); } interface ILeonardo : ITurtle {
我希望可用于 Java 的对象/关系映射 (ORM) 工具之一能够满足这些要求: 使用 JPA 或 native SQL 查询获取大量行并将其作为实体对象返回。 允许在行(实体)中进行迭代,并在对当前
好像没有,因为我有实现From for 的代码, 我可以转换 A到 B与 .into() , 但同样的事情不适用于 Vec .into()一个Vec . 要么我搞砸了阻止实现派生的事情,要么这不应该发
在 C# 中,如果 A 实现 IX 并且 B 继承自 A ,是否必然遵循 B 实现 IX?如果是,是因为 LSP 吗?之间有什么区别吗: 1. Interface IX; Class A : IX;
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在阅读标准haskell库的(^)的实现代码: (^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 a -> b ->a expo x0
我将把国际象棋游戏表示为 C++ 结构。我认为,最好的选择是树结构(因为在每个深度我们都有几个可能的移动)。 这是一个好的方法吗? struct TreeElement{ SomeMoveType
我正在为用户名数据库实现字符串匹配算法。我的方法采用现有的用户名数据库和用户想要的新用户名,然后检查用户名是否已被占用。如果采用该方法,则该方法应该返回带有数据库中未采用的数字的用户名。 例子: “贾
我正在尝试实现 Breadth-first search algorithm , 为了找到两个顶点之间的最短距离。我开发了一个 Queue 对象来保存和检索对象,并且我有一个二维数组来保存两个给定顶点
我目前正在 ika 中开发我的 Python 游戏,它使用 python 2.5 我决定为 AI 使用 A* 寻路。然而,我发现它对我的需要来说太慢了(3-4 个敌人可能会落后于游戏,但我想供应 4-
我正在寻找 Kademlia 的开源实现C/C++ 中的分布式哈希表。它必须是轻量级和跨平台的(win/linux/mac)。 它必须能够将信息发布到 DHT 并检索它。 最佳答案 OpenDHT是
我在一本书中读到这一行:-“当我们要求 C++ 实现运行程序时,它会通过调用此函数来实现。” 而且我想知道“C++ 实现”是什么意思或具体是什么。帮忙!? 最佳答案 “C++ 实现”是指编译器加上链接
我正在尝试使用分支定界的 C++ 实现这个背包问题。此网站上有一个 Java 版本:Implementing branch and bound for knapsack 我试图让我的 C++ 版本打印
在很多情况下,我需要在 C# 中访问合适的哈希算法,从重写 GetHashCode 到对数据执行快速比较/查找。 我发现 FNV 哈希是一种非常简单/好/快速的哈希算法。但是,我从未见过 C# 实现的
目录 LRU缓存替换策略 核心思想 不适用场景 算法基本实现 算法优化
1. 绪论 在前面文章中提到 空间直角坐标系相互转换 ,测绘坐标转换时,一般涉及到的情况是:两个直角坐标系的小角度转换。这个就是我们经常在测绘数据处理中,WGS-84坐标系、54北京坐标系
在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,我们在 .Net 7 下进行一次简单的演示. PeriodicTimer .
二分查找 二分查找算法,说白了就是在有序的数组里面给予一个存在数组里面的值key,然后将其先和数组中间的比较,如果key大于中间值,进行下一次mid后面的比较,直到找到相等的,就可以得到它的位置。
我是一名优秀的程序员,十分优秀!