当前位置: 首页 > news >正文

LRU缓存

        有人从网络读数据,有人从磁盘读数据,机智的人懂得合理利用缓存加速数据的读取效率,提升程序的性能,搏得上司的赏识,赢得白富美的青睐,进一步走向人生巅峰~

LRU假说

        LRU缓存(Least Recently Used Cache)即最近最少使用缓存算法,是一种常用的缓存淘汰策略,它基于这样一个假设:

如果数据最近被访问过,那么它在未来被访问的可能性也更高。

        因此,当缓存空间不足时,LRU缓存会优先移除最长时间未被访问的数据项。        

LRU是怎么干活的

新访问的数据添加到缓存

        当一个数据项被访问时,它会被添加到缓存中。如果该数据项已经在缓存中,它会被更新,并且移动到缓存的最前面,表示最近被访问过。

缓存满时移除最老的数据

        如果缓存已满(达到预设的容量限制),最久未被访问的数据项(位于缓存的最后面)会被移除,以便为新的数据项腾出空间。

维护访问顺序

        缓存需要维护数据项的访问顺序,以便快速确定哪些数据项是最近被访问的,哪些是最久未被访问的。

为了有效地实现LRU缓存,通常需要以下两种数据结构:
        双向链表:用于维护数据项的访问顺序。最近访问的数据项位于链表一头,最久未访问的数据项位于链表另一头。当数据项被访问时,它会被移动到链表最近访问那一头。当需要移除数据项时,最久未访问的末尾数据项会被移除。
        哈希表:用于存储键和指向双向链表中相应节点的指针,以便快速定位缓存中的数据项。这样可以在O(1)时间复杂度内访问缓存项。

LRU的简单示例

如下是一个简单的LRU实现

#include <iostream>
#include <list>
#include <string>
#include <unordered_map>
#include <vector>using namespace std;template <typename K, typename V>
class LRUCache {
public:LRUCache(int capacity) {cap = capacity;}V get(const K& key) {auto it = hash.find(key);if (it == hash.end()) {return V();}auto val = it->second->second;put(key, val);return val;}void put(const K& key, const V& value) {auto it = hash.find(key);if (it == hash.end()) {if (hash.size() >= cap) {auto d_it = data_list.begin();auto h_it = d_it->first;data_list.erase(d_it);hash.erase(h_it);}} else {auto d_it = it->second;data_list.erase(d_it);hash.erase(it);}data_list.emplace_back(key, value);hash[key] = --data_list.end();}private:int cap;list<pair<K, V> > data_list;using LIST_IT = typename list<pair<K, V> >::iterator;unordered_map<K, LIST_IT> hash;
};int main() {LRUCache<int, int> lru(2);vector<pair<string, vector<int> > > test_case = {{"put", {1, 1}},{"put", {2, 2}},{"get", {1}},{"put", {3, 3}},{"get", {2}},{"put", {4, 4}},{"get", {1}},{"get", {3}},{"get", {4}},};for (const auto& [opt, param] : test_case) {if (opt == "get") {auto val = lru.get(param.front());cout << val << endl;} else {lru.put(param.front(), param.back());}}return 0;
}

运行测试用例可以得到如下结果:

code % g++ lru.cpp -std=c++17
code % ./a.out       
1
0
0
3
4
code % 

        如上,实现一个LRU的代码量并不算多,并且简单易懂,性能也很不错,毕竟时间复杂度为O(1)。但LRU也有其缺点,例如它没有考虑数据的访问频率。这可能会导致一些不经常使用的数据被缓存,而一些经常使用的数据被淘汰

LRU的改进-LFU

        LFU(Least Frequently Used),即最少使用频率缓存,考虑到访问频率,而不是最近一次访问时间。其可以与LRU结合,形成其他变种,以更好地适应不同的数据访问模式。

LFU的简单示例

        例如,可以通过给LRU缓存数据项加上访问频率,当缓存满需要淘汰时,取尾部的数据选一个访问频次最低的来淘汰

#include <iostream>
#include <list>
#include <string>
#include <unordered_map>
#include <vector>using namespace std;template <typename K, typename V>
class LRUCache {
public:LRUCache(int capacity) {cap = capacity;}V get(const K& key) {auto it = hash.find(key);if (it == hash.end()) {return V();}const auto& data_tuple = *(it->second);auto val = std::get<1>(data_tuple);auto cnt = std::get<2>(data_tuple);put(key, val, cnt + 1);return val;}void put(const K& key, const V& value, int cnt = 1) {auto it = hash.find(key);if (it == hash.end()) {if (hash.size() >= cap) {remove_one_elem();}} else {auto d_it = it->second;data_list.erase(d_it);hash.erase(it);}data_list.emplace_back(key, value, cnt);hash[key] = --data_list.end();}private:void remove_one_elem() {auto need_rm = data_list.begin();auto it = need_rm;for (int i = 1; i < 3 && it != data_list.end(); ++i, ++it) {if (std::get<2>(*it) < std::get<2>(*need_rm)) {need_rm = it;}}hash.erase(std::get<0>(*need_rm));data_list.erase(need_rm);}private:int cap;list<tuple<K, V, int> > data_list;using LIST_IT = typename list<tuple<K, V, int> >::iterator;unordered_map<K, LIST_IT> hash;
};int main() {LRUCache<int, int> lru(2);vector<pair<string, vector<int> > > test_case = {{"put", {1, 1}},{"put", {2, 2}},{"get", {1}},{"put", {3, 3}},{"get", {2}},{"put", {4, 4}},{"get", {1}},{"get", {3}},{"get", {4}},};for (const auto& [opt, param] : test_case) {if (opt == "get") {auto val = lru.get(param.front());cout << val << endl;} else {lru.put(param.front(), param.back());}}return 0;
}

运行测试用例可以得到如下结果:

code % g++ lfu.cpp -std=c++17
code % ./a.out       
1
0
1
0
4
code % 

        与LRU示例的差异点在于,当缓存满时,LFU为从最近未使用的一头,挑选一个访问频次最小的元素进行淘汰。值得注意的是,挑选最少频次并不需要遍历所有的数据,而是针对具体的业务场景,设定一个合适的值即可。

        虽然LRU开销很小,时间复杂度又是O(1),但毕竟每次访问都需要调整链表,对于一些性能要求高的场景,负担还是有点重的,实际的使用场景中,又会根据具体的业务场景,做一些响应的改变。

衍生一下

Clock算法

        Clock算法是一种用于页面置换的缓存淘汰策略,它是LRU算法的一种近似实现,旨在降低实现LRU的开销。Clock算法有时也被称为Second-Chance算法,因为它给了每个页面一个“第二次机会”来避免被置换。

Clock是怎么干活的

        Clock算法维护一个循环链表:所有的页面都被组织成一个循环链表(或称为时钟结构),每个页面都有一个关联的访问位(通常是一个标志位),用于表示该页面自上次检查以来是否被访问过。

        使用指针指向链表中的一个页面:有一个指针(称为时钟指针)指向循环链表中的某个页面。

        维护访问位:当一个页面被访问时,其访问位被设置为1,表示该页面最近被使用过。

        缓存满时检查访问位:当有新需要加载到缓存中,但缓存已满,算法会检查当前时钟指针指向的页面的访问位。如果访问位为1,则将其清零(给予第二次机会),并将时钟指针移动到下一个页面。如果访问位为0,则选择该页面进行置换

        Clock算法的优点是实现简单,开销较低,因为它不需要像真正的LRU算法那样在每次页面访问时都对链表进行调整。它只需要在页面置换时检查和更新访问位。这使得Clock算法特别适合于大规模的缓存系统,如操作系统的页面缓存。

        Clock算法的缺点是它不是完全精确的LRU实现,因为它可能会保留一些不太常用的页面(如果它们在时钟指针到达之前刚好被访问过)。然而,对于许多实际应用来说,Clock算法提供了一个很好的折中方案,既保留了LRU的大部分优点,又显著降低了实现的复杂性和开销。

相关文章:

LRU缓存

有人从网络读数据&#xff0c;有人从磁盘读数据&#xff0c;机智的人懂得合理利用缓存加速数据的读取效率&#xff0c;提升程序的性能&#xff0c;搏得上司的赏识&#xff0c;赢得白富美的青睐&#xff0c;进一步走向人生巅峰~ LRU假说 LRU缓存&#xff08;Least Recently Used…...

ncc匹配提速总结

我们ncc最原始的匹配方法是&#xff1a;学习模板w*h个像素都要带入ncc公式计算 第一种提速&#xff0c;学习模板是w*h&#xff0c;而我们支取其中的w/2*h/2,匹配窗口同理&#xff0c;计算量只有1/4。 另外一种因为ncc是线性匹配&#xff0c;我们在这上面也做了文章&#xff0…...

人力资源智能化管理项目(day06:员工管理)

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/humanResourceIntelligentManagementProject 页面结构 <template><div class"container"><div class"app-container"><div class"left"><el-input style&qu…...

Java实现数据可视化的智慧河南大屏 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…...

【Flink】FlinkSQL的DataGen连接器(测试利器)

简介 我们在实际开发过程中可以使用FlinkSQL的DataGen连接器实现FlinkSQL的批或者流模拟数据生成,DataGen 连接器允许按数据生成规则进行读取,但注意:DataGen连接器不支持复杂类型: Array,Map,Row。 请用计算列构造这些类型 创建有界DataGen表 CREATE TABLE test ( a…...

5G NR 频率计算

5G中引入了频率栅格的概念&#xff0c;也就是小区中心频点和SSB的频域位置不能随意配置&#xff0c;必须满足一定规律&#xff0c;主要目的是为了UE能快速的搜索小区&#xff1b;其中三个最重要的概念是Channel raster 、synchronization raster和pointA。 1、Channel raster …...

关于物理机ping不通虚拟机问题

方法一 设置虚拟机处于桥接状态即可&#xff1a;&#xff08;虚拟机->设置->网络适配器&#xff09;&#xff0c;选择完确定&#xff0c;重启虚拟机即可。 方法二 如果以上配置还是无法ping通&#xff1a;&#xff08;编辑->虚拟网络编辑器&#xff09; 首先查看主机网…...

深度学习在知识图谱问答中的革新与挑战

目录 前言1 背景知识2 基于深度学习改进问句解析模型2.1 谓词匹配2.2 问句解析2.3 逐步生成查询图 3 基于深度学习的端到端模型3.1 端到端框架3.2 简单嵌入技术 4 优势4.1 深入的问题表示4.2 实体关系表示深挖4.3 候选答案排序效果好 5 挑战5.1 依赖大量训练语料5.2 推理类问句…...

JAVA设计模式之职责链模式详解

职责链模式 1 职责链模式介绍 职责链模式(chain of responsibility pattern) 定义: 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止. 在职责链模式中&#xff0c…...

CSP-201912-1-报数

CSP-201912-1-报数 知识点总结 整数转化为字符串#include <string> string str_num to_string(num);字符串中查找是否包含字符‘7’&#xff1a;str_num.find(7) 未找到返回-1找到返回返回该字符在字符串中的位置&#xff08;即第一次出现的索引位置&#xff09; #i…...

前后端分离好处多多,怕就怕分工不分人,哈哈

前后端分离倡导多年了&#xff0c;现在基本成为了开发的主流模式了&#xff0c;贝格前端工场承接的前端项目只要不考虑seo的&#xff0c;都采用前后端分离模式&#xff0c;这篇文章就来介绍一下前后端分离模式。 一、什么是前后端分离开发模式 前后端分离是一种软件开发的架构…...

机器学习:Softmax介绍及代码实现

Softmax原理 Softmax函数用于将分类结果归一化&#xff0c;形成一个概率分布。作用类似于二分类中的Sigmoid函数。 对于一个k维向量z&#xff0c;我们想把这个结果转换为一个k个类别的概率分布p(z)。softmax可以用于实现上述结果&#xff0c;具体计算公式为&#xff1a; 对于…...

python基于flask的网上订餐系统769b9-django+vue

课题主要分为两大模块&#xff1a;即管理员模块和用户模块&#xff0c;主要功能包括个人中心、用户管理、菜品类型管理、菜品信息管理、留言反馈、在线交流、系统管理、订单管理等&#xff1b; 如果用户想要交换信息&#xff0c;他们需要满足双方交换信息的需要。由于时间有限…...

jenkins 发布远程服务器并部署项目

安装参考另一个文章 配置maven 和 jdk 和 git 注意jdk的安装目录&#xff0c;是jenkins 安装所在服务器的jdk目录 注意maven的目录 是jenkins 安装所在服务器的maven目录 注意git的目录 是jenkins 安装所在服务器的 git 目录 安装 Publish Over SSH 插件 配置远程服务器 创…...

【数学建模】【2024年】【第40届】【MCM/ICM】【D题 五大湖的水位控制问题】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem D: Great Lakes Water Problem Background The Great Lakes of the United States and Canada are the largest group of freshwater lakes in the world. The five lakes and connecting waterways const…...

【开源】JAVA+Vue+SpringBoot实现公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…...

###C语言程序设计-----C语言学习(12)#进制间转换,十进制,二进制,八进制,十六进制

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 计算机处理的所有信息都以二进制形式表示&#xff0c;即数据的存储和计算都采…...

锐捷设备常用命令

一、命令模式 命令行主要有用户模式、特权模式、全局模式、VLAN模式、接口模式、线程模式 switch> "用户模式"switch# "特权模式"switch(config) "全局模式"switch(conf…...

python:lxml 读目录.txt文件,用 xmltodict 转换为json数据,生成jstree所需的文件

请参阅&#xff1a;java : pdfbox 读取 PDF文件内书签 请注意&#xff1a;书的目录.txt 编码&#xff1a;UTF-8&#xff0c;推荐用 Notepad 转换编码。 pip install lxml ; lxml-5.1.0-cp310-cp310-win_amd64.whl (3.9 MB) pip install xmltodict ; lxml 读目录.txt文件&…...

【Spring】Spring 对 Ioc 的实现

一、Ioc 控制反转 控制反转是一种思想 控制反转是为了降低程序耦合度&#xff0c;提高程序扩展力&#xff0c;达到 OCP 原则&#xff0c;达到 DIP 原则 控制反转&#xff0c;反转的是什么&#xff1f; 将对象的创建权利交出去&#xff0c;交给第三方容器负责 将对象和对象之…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...