高阶数据结构——LRU Cache

news2024/9/21 13:35:14

1.什么是LRU Cache

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。

例如:我们现在要打开微信,操作系统会从赋存中将微信相关的代码数据加载到内存中,CPU读取内存中的数据即可,但是CPU的速度是非常快的,相对于CPU来说,内存的速度就很慢了。

为了解决这个问题,会在CPU与内存之间增加一个Cache来协调两者的速度差异。当我们在使用微信的视频聊天功能时,CPU下次读取的指令大概率还是视频聊天,所以可以预先加载一部分视频聊天的数据或者代码到Cache中去。Cache要比内存快得多,能更好的配合CPU工作。
空间局部性原理:现在正在使用的数据和将来要使用的数据在存储位置上可能是相邻的。

而Cache就是建立在局部性原理上工作的。

2. Cache的替换算法

计算机的硬件设计上,速度越快的,造价越高,容量越小,Cache的速度虽然快,但是容量较小,如果Cache满了,现在需要将新的内容加载到Cache中,则需要进行替换,有四种比较常见的Cache替换算法。

  • 1.随机算法(RAND)
  • 2.先进先出算法(FIFO)
  • 3.近期最少使用(LRU)
  • 4.最近不经常使用(LFU)

2.1 随机算法(Rand)

在Cache满了时,随机选择一块进行替换。

假设现在一共有4个Cache块,以下是CPU访问的主存块号顺序。{1,2,3,4,1,2,5,1,2,3,4,5}

1,2,3,4号主存块直接插入即可,因为此时Cache还没有满,接下来的1,2号主存块因为Cache中已经存在,所以直接访问Cache中的即可。

到访问5号内存块时,因为Cache中没有,所以要进行替换,由于当前是随机替换,这里就替换3号Cache。

再接下来的1,2号都可以直接命中。

3号主存块未命中,所以需要替换一个Cache块,我们这里替换4号。

4号主存块也是一样,未命中随机替换一个Cache块

最后一个5命中了,不需要替换。

随机算法很简单,但是不稳定,没有考虑到局部性原理,命中率较低。

2.2 先进先出算法(FIFO)

顾名思义,就是先进入Cache的内存块先被替换掉。

还是和上面一样,将主存块号为{1,2,3,4,1,2,5,1,2,3,4,5}依次插入Cache中。

前面的1,2,3,4可以直接插入,因为此时的Cache还未满。

接下来的1,2号主存块命中了。

访问5号主存块时,5号主存块未命中,所以此时需要进行替换,这里的算法是先进先出,所以5号主存块将1号Cache块替换掉了。

接下来的2号结点,因为也未命中Cache,所以需要进行替换,目前最新进入Cache的是3号,所以我们将3号替换成2号。

接下来和前面一样,这里不过多赘述。

和随机算法一样,实现简单,没有考虑局部性原理,最先进入的也有可能是最频繁访问的。

2.3 近期最少使用算法(LRU)

为每一个Cache设置一个计数器,记录每个Cache多久没有被访问了,替换时优先选择计数值最大的那个Cache。

每次访问Cache时,有以下三种情况。

  • 命中时,所命中的行的计算器清零,比其低的计算器加1,其余不变
  • 未命中且还有空闲行时,新装入的计数器为0,其余非空闲行全加1;
  • 未命中且无空闲行时,将计数值最大的行淘汰,新装行的计数器置为0,其余全加1

CPU依次访问主存块号为{1,2,3,4,1,2,5,1,2,3,4,5}。

前面4个主存块直接插入即可,因为Cache未满,并且还要更新计数器。

接下俩访问的是1号主存块,由于Cache中已经存在1号主存块,所以不需要替换,我们再来看看当命中时需要怎么做。

命中时,所命中的行的计算器清零,比其低的计算器加1,其余不变

所以我们需要更改计数器的值,如上图所示。

接下来访问的是2号主存块,和前面一样,Cache命中,将对应的计数器清零,其他计数器加1。

访问5号主存块时,Cache未命中,需要进行替换,找到计数器最大的Cache进行替换,也就是将3号替换成5,并将对应的计数器清零,其他的加1。

访问1号主存块,Cache命中,将对应计数器置为0,并将比他低的计数器加1,所以4号主存块的计数器不需要加1。

我们当然可以将4号主存块也加1,但是没有必要,因为4号主存块的计数器已经是最大了,没有必要再加1,这样子设计有个好处,就是如果一共有2^n个Cache块,那么只需要n位技术器就可以,在顶层设计硬件时更加简单。

此时访问2号主存块也是一样,Cache命中,将对应计数器置0,将比2号小的计数器值加1。

访问3号时,Cache没有命中,将计数器最大的主存块,也就是4号替换成3号,其余的计数器加1.

访问4号主存块,Cache未命中,将计数器最大的5号替换成4号,其余计数器加1

访问5号主存块,Cache未命中,将计数器最大的1号换成5号,其余的计数器加1。

LRU算法是基于局部性原理的,近期被访问过的主存块,在将来很有可能再次被访问。LRU算法实际运行的效果优秀,Cache命中率高。

但是如果频繁访问的主存块数量 > Cache行的数量时,可能会发生"抖动",例如Cache行的数量只要4时,遇到如下{1,2,3,4,5,1,2,3,4,5,1,2,3,4,5....}。虽然每个内存块都会频繁访问,但是Cache行的数量较小,所以也会被替换。

2.4 最不经常使用算法(LFU)

为每一个Cache设置一个计数器,记录每个Cache被访问的次数,满了后替换计数器最小的。

设一共有4个Cache块,依次访问主存块{1,2,3,4,1,2,5,1,2,3,4,5}。

前面几个还是一样。

访问5号主存块时,Cache未命中需要进行替换,找到计数器值最小的进行替换,也就是将3号替换成5号。

接下来的1,2都会命中,直接将计数器+1即可。

访问3号主存块时,Cache未命中需要进行替换,找到计数器值最小的进行替换,也就是将5号替换成3号。

Cache命中,计数器+1

访问5号主存块时,Cache未命中需要进行替换,找到计数器值最小的进行替换,也就是将3号替换成5号。

LFU是基于曾经被经常访问,在未来不一定会用到,没有很好遵循局部性原理,所以实际效率不如LRU。

3. LRU Cache的实现

实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。

下面以一道oj题带大家自己实现一个Cache。

题目链接:146. LRU 缓存 - 力扣(LeetCode)

要让get和put都在O(1)时间内完成,我们可以借助哈希表来完成,因为哈希表的查找和插入效率都是O(1),也就是说get时可以做到O(1),而put的大多数场景也可以做到,之所以说大多数就是因为put时可能Cache未命中,此时需要替换出计数器最大的那个数,如果只有一个哈希表,是无法在O(1)的时间内找到计数器最大的值,所以我们添加一个链表。

链表的尾部保存的是最近最少使用的key,如果一个数被使用了,那么我们就将这个数提到链表的头部,哈希表保存的是key和key在链表中的位置。

代码如下:

class LRUCache {
public:
    LRUCache(int size) {
        capacity = size;
    }
    
    int get(int key) {
        auto find = hashMap.find(key);
        if (find == hashMap.end())
        {
            return -1;
        }
        else 
        {
            //更新key的位置。
            Iterator it = find->second;
            //将结点挪到链表头部
            LRUList.splice(LRUList.begin(), LRUList, it);
            return it->second;
        }
    }
    
    void put(int key, int value) {
        auto find = hashMap.find(key);
        if (find == hashMap.end())
        {
            //没找到,将这个结点插入Cache中。
            if (LRUList.size() == capacity)
            {
                //如果Cache满了,就将使用最少的删除。
                pair<int, int> end = LRUList.back();
                LRUList.pop_back();
                hashMap.erase(end.first);
            }

            //将key插入Cache中
            LRUList.push_front({key, value});
            hashMap.insert({key, LRUList.begin()});
        }
        else 
        {
            //找到了,更新他的值
            auto it = find->second;
            it->second = value;

            //将该结点放至链表开头
            LRUList.splice(LRUList.begin(), LRUList, it);
        }
    }

    typedef list<pair<int, int>>::iterator Iterator;
    unordered_map<int, Iterator> hashMap;
    list<pair<int, int>> LRUList;
    size_t capacity;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2033149.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

制约AI发展的关键在于人机环境系统智能的失配

人工智能&#xff08;AI&#xff09;发展的关键挑战之一就是人机环境系统之间的智能失配。这种失配指的是人工智能系统、其操作人员和应用环境之间的协调和适配问题&#xff0c;通常会影响系统的有效性和安全性。以下是一些具体方面&#xff0c;这些方面展示了人机环境系统智能…

《企业微服务实战 · 接口鉴权思路分享》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

ES JavaApi

1.RestClient操作索引库 2.RestClient操作文档 2.1查询 2.2更新 2.3删除 2.4批量新增&#xff08;bulk&#xff09; 3.DSL查询 对应的api 3.0解析响应 3.1全文检索 3.2精确查询 3.3复合查询-boolQuery 构建boolQuery 3.4排序和分页 3.5高亮

浙大数据结构慕课课后题(06-图2 Saving James Bond - Easy Version)(拯救007)

题目要求&#xff1a; This time let us consider the situation in the movie "Live and Let Die" in which James Bond, the worlds most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake fi…

C++打怪小游戏

这是一款用C代码写出来的打怪游戏。 上图片&#x1f447; ![](https://i-blog.csdnimg.cn/direct/6a4497c784ff4ba7a3332bc97d433789.png 一个11岁小朋友&#xff0c;爆肝532行&#xff0c;11小时完成代码&#xff0c;内部14个函数&#xff0c;5个结构体&#xff0c;三连…

ffmpeg使用x11录屏

version #define FFMPEG_VERSION "6.1.1" note x11视频采集结构:AVInputFormat ff_xcbgrab_demuxer code void CFfmpegOps::CaptureVideo(const char *outFileName) {const AVInputFormat *iFmt nullptr;size_t n 0;AVFormatContext *iFmtCtx nullptr;AVDict…

三十九、大数据技术之Kafka3.x(2)

&#x1f33b;&#x1f33b; 目录 一、Kafka 生产者1.1 生产者消息发送流程1.1.1 发送原理1.1.2 生产者重要参数列表 1.2 异步发送API1.2.1 普通异步发送1.2.2 带回调函数的异步发送 1.3 同步发送 API1.4 生产者分区1.4.1 分区好处1.4.2 生产者发送消息的分区策略1.4.3 自定义分…

使用Leaks定位iOS内存泄漏问题并解决

使用Leaks定位iOS内存泄漏问题并解决 前言 内存泄漏问题一直是程序开发中最令人头疼的问题&#xff0c;特别是C/C。虽然C/C在C11之后引入了许多新特性&#xff0c;包括智能指针&#xff0c;自动类型推导等&#xff0c;但C中动态内存的分配和释放仍然需要程序员来显式地进行。…

Linux线程thread详解(线程池)

在我们的进程虚拟地址的代码区&#xff0c;对于代码中的每个函数都有对应的地址&#xff0c;每个函数中的每行代码都有对应的代码&#xff0c;并且每个函数中的每行代码的地址都是连续的。既然代码是连续的&#xff0c;也就意味着我们可以将我们代码分块&#xff0c;分成不同的…

机器学习笔记:序列到序列学习[详细解释]

介绍 本节我们使用两个循环神经网络的编码器和解码器&#xff0c; 并将其应用于序列到序列&#xff08;sequence to sequence&#xff0c;seq2seq&#xff09;类的学习任务。遵循编码器&#xff0d;解码器架构的设计原则&#xff0c; 循环神经网络编码器使用长度可变的序列作为…

Jeecgboot3.6.3的vue3版本的一种flowable动态增加一个用户任务节点的方法(二)前端代码实现

因为这个项目license问题无法开源,更多技术支持与服务请加入我的知识星球。 这部分主要讲前端的功能实现 1、前端选择新增任务类型界面,点击新增节点 /*** 动态新增用户任务节点*/function handleAddTask(record: Recordable) {if (record.finishTime != null) {createMess…

在 .NET 8.0 中使用 xUnit 进行数据驱动测试

1. 前言 xUnit是一个功能强大且易于使用的单元测试框架。在.NET开发中&#xff0c;单元测试是非常重要的一部分&#xff0c;它可以帮助我们确保代码的正确性和可靠性。使用xUnit可以帮助我们编写更高效、更有效的单元测试&#xff0c;并提高代码质量和可维护性。 2. 特性 x…

Git-GitLab-Jenkins结合

目录 1.Git-GitLab-Jenkins结合2. 在pycharm配置git3. 实现提交代码后触发自动化测试&#xff08;1&#xff09;打开gitlab&#xff08;2&#xff09;Jenkins配置Git&#xff08;3&#xff09;选择需要的远程仓库 4.报告存在问题&#xff1a;5.也可以在Jenkins中设置定时触发&a…

OpenCV图像滤波(10)Laplacian函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 功能描述 计算图像的拉普拉斯值。 该函数通过使用 Sobel 运算符计算出的 x 和 y 的二阶导数之和来计算源图像的拉普拉斯值&#xff1a; dst Δ src ∂…

Elasticsearch:引入 Serverless 精简索引分片

作者&#xff1a;来自 Elastic Tanguy Leroux 在本文中&#xff0c;我们将介绍 Elasticsearch 的精简索引分片&#xff08;thin indexing shards&#xff09;&#xff0c;这是我们为 Elastic Cloud Serverless 开发的一种新型分片&#xff0c;允许将 Elasticsearch 索引存储在云…

大数据技术现场工程师特色实训室解决方案

一、引言 在大数据时代背景下&#xff0c;数据已成为新的生产要素&#xff0c;驱动着各行各业的创新发展。面对这一趋势&#xff0c;市场对于既掌握大数据理论知识又具备实战能力的大数据技术人才的需求急剧增加。为了应对这一挑战&#xff0c;唯众精心设计了一套全面的大数据…

国产 麒麟 ARM 环境编译 RocketMQ-Client-CPP

1.环境 系统版本&#xff1a;Linux 5.4.18-87.76-generic KYLINOS SMP Thu Aug 31 09:05:44 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux GCC: gcc (Ubuntu 9.3.0-10kylin2) 9.3.0 G: g (Ubuntu 9.3.0-10kylin2) 9.3.0 RocketMQ服务端版本&#xff1a;5.1.1 RocketMQ-cpp …

修改docker的/var/lib/docker/overlay2储存路径

目录 目录 1.准备新的存储位置 1.创建新的存储目录 2.修改目录权限 2. 配置 Docker 使用新的存储位置 1.停止 Docker 服务 2.编辑 Docker 配置文件 3.迁移现有 Docker 数据 1.将现有的 Docker 数据从系统盘移动到新目录 2.启动 Docker 服务 3. 验证更改 4. 清理旧的…

RAGFlow v0.9 重磅升级,支持 GraphRAG,开启下一代 RAG 之旅!

一、引言 前面我们介绍过很多的关于大模型和RAG相关的技术&#xff0c;通过其关注程度足以看到市场上对RAG框架和成熟产品的迫切需求&#xff0c;因为想要个人独立从0开始实现一个RAG产品并非易事&#xff0c;虽然有相当多的RAG或者知识库开源产品&#xff0c;大部分其实很难应…

使用 Elasticsearch RestHighLevelClient 进行查询

Elasticsearch 提供了多种客户端库&#xff0c;以方便不同编程语言的用户进行操作。其中&#xff0c;Java 的 RestHighLevelClient 是 Elasticsearch 官方推荐的客户端之一&#xff0c;用于 Java 应用程序中。本文将介绍如何使用 Java 的 RestHighLevelClient 进行 Elasticsear…