LRU 缓冲池 (不考虑多线程)
lru:(转)LRU算法的实现
什么是LRU算法? LRU是Least Recently Used的缩写,即最近最少使用页面置换算法,是为虚拟页式存储管理服务的。
关于操作系统的内存管理,如何节省利用容量不大的内存为最多的进程提供资源,一直是研究的重要方向。而内存的虚拟存储管理,是现在最通用,最成功的方式——在内存有限的情况下,扩展一部分外存作为虚拟内存,真正的内存只存储当前运行时所用得到信息。这无疑极大地扩充了内存的功能,极大地提高了计算机的并发度。虚拟页式存储管理,则是将进程所需空间划分为多个页面,内存中只存放当前所需页面,其余页面放入外存的管理方式。
然而,有利就有弊,虚拟页式存储管理减少了进程所需的内存空间,却也带来了运行时间变长这一缺点:进程运行过程中,不可避免地要把在外存中存放的一些信息和内存中已有的进行交换,由于外存的低速,这一步骤所花费的时间不可忽略。因而,采取尽量好的算法以减少读取外存的次数,也是相当有意义的事情。
对于虚拟页式存储,内外存信息的替换是以页面为单位进行的——当需要一个放在外存的页面时,把它调入内存,同时为了保持原有空间的大小,还要把一个内存中页面调出至外存。自然,这种调动越少,进程执行的效率也就越高。那么,把哪个页面调出去可以达到调动尽量少的目的?我们需要一个算法。
自然,达到这样一种情形的算法是最理想的了——每次调换出的页面是所有内存页面中最迟将被使用的——这可以最大限度的推迟页面调换,这种算法,被称为理想页面置换算法。可惜的是,这种算法是无法实现的。
为了尽量减少与理想算法的差距,产生了各种精妙的算法,最近最少使用页面置换算法便是其中一个。LRU算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的页面很可能在后面的几条指令中频繁使用。反过来说,已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到。这个,就是著名的局部性原理——比内存速度还要快的cache,也是基于同样的原理运行的。因此,我们只需要在每次调换时,找到最近最少使用的那个页面调出内存。这就是LRU算法的全部内容。
如何用具体的数据结构来实现这个算法?
首先,最容易想到,也最简单的方法:计时法。给页表中的每一页增加一个域,专门用来存放计时标志,用来记录该页面自上次被访问以来所经历的时间。页面每被访问一次,计时清0。要装入新页时,从内存的页面中选出时间最长的一页,调出,同时把各页的计时标志全部清0,重新开始计时。
计时法可以稍作改变,成为计数法:页面被访问,计数标志清0,其余所有内存页面计数器加1;要装入新页时,选出计数最大的一页调出,同时所有计数器清0。
这两种方法确实很简单,但运行效率却不尽如人意。每个时刻,或是每调用一个页面,就需要对内存中所有页面的访问情况进行记录和更新,麻烦且开销相当大。
另一种实现的方法:链表法。
操作系统为每个进程维护一条链表,链表的每个结点记录一张页面的地址。调用一次页面,则把该页面的结点从链中取出,放到链尾;要装入新页,则把链头的页面调出,同时生成调入页面的结点,放到链尾。
链表法可看作简单计时/计数法的改良,维护一个链表,自然要比维护所有页面标志要简单和轻松。可是,这并没有在数量级上改变算法的时间复杂度,每调用一个页面,都要在链表中搜寻对应结点并放至链尾的工作量并不算小。
以上是单纯使用软件实现的算法。不过,如果能有特殊的硬件帮忙,我们可以有更有效率的算法。
首先,如果硬件有一个64位的计数器,每条指令执行完后自动加1。在每个页表项里添加一个域,用于存放计数器的值。进程运行,每次访问页面的时候,都把计数器的值保存在被访问页面的页表项中。一旦发生缺页,操作系统检查页表中所有的计数器的值以找出最小的一个,那这一页就是最久未使用的页面,调出即可。
其次,另外一个矩阵算法:在一个有n个页框的机器中,LRU硬件可以维持一个n*n的矩阵,开始时所有位都是0。访问到第k页时,硬件把k行的位全设为1,之后再把k列的位置设为0。容易证明,在任意时候,二进制值最小的行即为最久未使用的页面,当调换页面时,将其调出。
以上的两种算法,无疑都要比纯粹的软件算法方便且快捷。每次页面访问之后的操作——保存计数器值和设置k行k列的值,时间复杂度都是O(1)量级,与纯软件算法不可同日而语。
那是否软件算法就毫无用处?当然不是,硬件算法有它致命的缺陷,那就是需要硬件的支持才能运行。如果机器上恰好有所需硬件,那无疑是再好不过;反之,若机器上没有这种硬件条件,我们也只能无奈地抛弃硬件算法,转而选取相对麻烦的软件算法了。
最后,让我们来谈论一下LRU算法。首先,这是一个相当好的算法,它是理想算法很好的近似。在计算机系统中应用广泛的局部性原理给它打造了坚实的理论基础,而在实际运用中,这一算法也被证明拥有极高的性能。在大规模的程序运行中,它产生的缺页中断次数已很接近理想算法。或许我们还能找到更好的算法,但我想,得到的收益与付出的代价恐怕就不成比例了。当然,LRU算法的缺点在于实现方法的不足——效率高的硬件算法通常在大多数机器上无法运行,而软件算法明显有太多的开销。与之相对的,FIFO算法,和与LRU相似的NRU算法,性能尽管不是最好,却更容易实现。所以,找到一个优秀的算法来实现LRU,就是一个非常有意义的问题。
根据上文,链表需要每次get的时候查找,因此降低了效率,一般面试时 都是用双链表和HASH表来实现,因此设计了利用hash表来查找链表,提高效率。
代码如下:
#include <iostream>
#include <hash_map> using namespace std; class LRUCache{
int maxNum;
//链表节点
struct Entity
{
Entity * beforeNode;
Entity * afterNode; int key; Entity(int newkey)
{
key=newkey;
beforeNode=NULL;
afterNode=NULL;
}
};
//存储数据和节点地址
struct Unio
{
int data;
Entity * eNode;
Unio (int newdata,int key)
{
data=newdata;
eNode=new Entity(key);
} ~Unio()
{
delete eNode;
}
}; hash_map<int,Unio *> hashCache;
Entity * headPtr;
Entity * endPtr; public:
LRUCache(int capacity) {
maxNum=capacity;
headPtr=NULL;
endPtr=NULL;
} ~LRUCache() { hash_map<int, Unio *>::iterator it=hashCache.begin();
for(;it!=hashCache.end();++it)
{
delete it->second;
}
} int get(int key) {
hash_map<int ,Unio *>::iterator it=hashCache.find(key);
if(it!=hashCache.end())
{ Entity * temPtr=it->second->eNode;
if(headPtr!=NULL)
{
if(headPtr->key!=temPtr->key)
{
Entity * beforeP=temPtr->beforeNode;
Entity * afterP=temPtr->afterNode; if(beforeP!=NULL)
{
beforeP->afterNode=afterP; } if(endPtr->key==temPtr->key)
{
endPtr=beforeP;
} if(afterP!=NULL)
{
afterP->beforeNode=beforeP;
} if(headPtr!=NULL)
{
temPtr->afterNode=headPtr;
headPtr->beforeNode=temPtr;
} temPtr->beforeNode=NULL;
headPtr=temPtr;
}
}
return it->second->data; }
return ;
} void set(int key, int value) { hash_map<int ,Unio *>::iterator it=hashCache.find(key);
if(it!=hashCache.end())
{
it->second->data=value;
}
else
{
Unio * p=new Unio(value,key);
if(hashCache.size()==maxNum)
{
if(endPtr!=NULL)
{
int key=endPtr->key; Entity * bp=endPtr->beforeNode;
if(bp!=NULL)
{
bp->afterNode=NULL;
}
endPtr=bp;
if(headPtr->key==key)
{
headPtr=endPtr;
} hash_map<int,Unio *> ::iterator it=hashCache.find(key);
Unio * up=it->second;
delete up;
hashCache.erase(it);
}
else
{
return;
} }
Entity * pnode=p->eNode;
if(endPtr!=NULL)
{
endPtr->afterNode=pnode;
pnode->beforeNode=endPtr;
}
endPtr=pnode;
if(headPtr==NULL)
{
headPtr=endPtr;
}
hashCache[key]=p; }
} void OutPut()
{
Entity * x=headPtr;
while (NULL!=x)
{
cout<<x->key<<" ";
x=x->afterNode;
}
cout<<endl;
}
}; int main()
{
LRUCache cache();
cache.set(,);
cache.set(,);
cache.OutPut();
cout<<cache.get()<<endl; cache.set(,);
cache.OutPut(); system("pause"); return ;
}
这样使用get方法去更新链表时 复杂度为O(1),提高了效率。
LRU 缓冲池 (不考虑多线程)的更多相关文章
- MySql 缓冲池(buffer pool) 和 写缓存(change buffer) 转
应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库. 操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问. M ...
- InnoDB存储引擎内存缓冲池管理技术——LRU List、Free List、Flush List
InnoDB是事务安全的MySQL存储引擎,野山谷OLTP应用中核心表的首选存储引擎.他是基于表的存储引擎,而不是基于数据库的.其特点是行锁设计.支持MVCC.支持外键.提供一致性非锁定读,同时被设计 ...
- InnoDB的后台线程(IO线程,master线程,锁监控线程,错误监控线程)和内存(缓冲池,重做日志缓冲池,额外内存池)
InnoDB有多个内存块,你可以认为这些内存块组成了一个大的内存池,负责如下工作: 维护所有进程/线程需要访问的多个内部数据结构. 缓存磁盘上的数据,方便快速地读取,并且在对磁盘文件的数据进行修改之前 ...
- .NET基础拾遗(5)多线程开发基础
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- java多线程详解(7)-线程池的使用
在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, 这样频繁创建线程就会大大降低系 ...
- 中控考勤仪IFace302多线程操作时无法订阅事件
场景: 在各办事点安装中控考勤仪Iface302,各办事点的工作人员上下班报到时使用指纹或面纹进行自动登记,验证成功后将与服务吕进行通讯记录相关的考勤信息. 条件限制: 由于Iface302设备不支持 ...
- InnoDB源码分析--缓冲池(二)
转载请附原文链接:http://www.cnblogs.com/wingsless/p/5578727.html 上一篇中我简单的分析了一下InnoDB缓冲池LRU算法的相关源码,其实说不上是分析,应 ...
- innoDB源码分析--缓冲池
最开始学Oracle的时候,有个概念叫SGA和PGA,是非常重要的概念,其实就是内存中的缓冲池.InnoDB的设计类似于Oracle,也会在内存中开辟一片缓冲池.众所周知,CPU的速度和磁盘的IO速度 ...
- LRU缓存实现(Java)
LRU Cache的LinkedHashMap实现 LRU Cache的链表+HashMap实现 LinkedHashMap的FIFO实现 调用示例 LRU是Least Recently Used 的 ...
随机推荐
- Codeforces Round #80 Div.1 D
思路:考虑离线操作,以y为关键字排序,对于y相同的一起操作,然后考虑y的范围,当y<=sqrt(n)时,直接O(n)预处理出f[x]表示f[x]+f[x+y]+f[x+2*y]+..+f[x+k ...
- (转)iOS中3种正则表达式的使用与比较
.利用NSPredicate(谓词)匹配 例如匹配有效邮箱: NSString *email = @“nijino_saki@.com”: NSString *regex = @"[A-Z0 ...
- python中的gil是什么?
1.gil是什么? 在Python源代码:Python-2.7.10/Python/ceval.c.我看到的Python源代码版本为2.7.10 static PyThread_type_lock i ...
- 动态改变EasyUI grid 列宽和隐藏列
隐藏显示 $('#yourGrid').datagrid('hideColumn','yourColumn'); $('#yourGrid').datagrid('hideColumn','yourC ...
- important的妙用
!important: 为某些样式设置具有最高权值,高于id选择器 用法: !important要写在分号的前面 例如: <p class="first">!impor ...
- js点击 密码输入框密码显示隐藏
很多密码框都有个眼睛标记,点击能显示密码.原理就是点击切换password为text等显示 下面上代码 <!DOCTYPE html> <html> <head> ...
- jQuery插件综合应用(一)注册
一.介绍 注册和登录是每个稍微有点规模的网站就应该有的功能.登陆功能与注册功能类似,也比注册功能要简单些.所以本文就以注册来说明jQuery插件的应用. jQuery插件的使用非常简单,如果只按照jQ ...
- easy ui tree 取复选框打勾的值
var nodes = $('#basetree').tree('getChecked'); var cnode = ''; for ( var i = 0; i < nodes.length; ...
- 结构型模式(Structural patterns)->外观模式(Facade Pattern)
动机(Motivate): 在软件开发系统中,客户程序经常会与复杂系统的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化.那么如何简化客户程序与子系统之间的交互接口?如何将复杂系统的内部子 ...
- 跨线程操作UI控件
写程序的时候经常会遇到跨线程访问控件的问题,看到不少人去设置Control.CheckForIllegalCrossThreadCalls = false;这句话是告诉编译器不要对跨线程访问作检查,可 ...