从LIRS算法的描述来看,可以理解为两个LRU队列的组合,利用cold缓冲区来保护Hot缓冲区,提高了进入hot缓冲区的门槛,阻止hot缓冲区频繁地变化。为了验证LIRS算法的有效性,笔者自行开发了LRU算法及LIRS算法的c#实现。
首先说明LRU算法的实现。
采用带表头的单链表来存储数据节点,head指针指向表头,tail指针指向链表最后一个节点。数据节点则记录了每个缓存块的ID,加载状态,数据指针,next指针。另有unUsedSize变量存放未分配的缓存块数量。
当用户访问命中时,将对应的数据节点移到队列尾部。当用户访问未命中时,若能分配新数据节点,则分配,否则从表头摘下一个数据节点,并卸载数据。更新得到的数据节点的信息,并加到队列尾部。
最后,为了避免在查找数据节点时遍历整个链表,增加了Dictionary<ID,数据节点>的字典,利用其hash查找功能提高查找效率。
再说明LIRS算法的实现。
为了简化实现,将算法实现为多个逻辑LRU队列(可实现多级缓冲区),在物理实现上,仍采用带表头的单链表来存储数据节点。对于N级缓冲区的实现(原始的LIRS算法为2级),使用unUsedSize[N]数组存放每个缓冲区的未分配数量,使用head[N+1]存放缓冲区的头尾指针。这样head[0]、head[1]指示了第0块缓冲区的头尾,依次类推,head[N-1]、head[N]指示了第N-1块缓冲区的头尾。0号缓冲区是cold的缓冲区,N-1号则是最hot的。由于使用了多级缓冲区,数据节点也需要增加一个选择器selector,用于指示该节点属于哪个缓冲区。
当用户访问未命中,分配新节点时,先检查N-1号缓冲区是否能分配新节点,若不能则依次向前检查。若分配了新节点,则直接加入相应缓冲区的尾部,否则就从0级缓冲区头部摘下一个节点来,更新节点信息后加入0级缓冲区尾部。
当用户访问命中时,则将x级的节点提升到x+1级,并加到缓冲区尾部,x+1级的头部节点,则被移到x级缓冲区的尾部
同样的,增加了Dictionary<ID,数据节点>字典,提高查找节点的效率。
using System;
using System.Linq;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
//using System.Collections.Concurrent;
using WaveTang.Classes;
public interface ICache<T,U>{
//T=资源类型,U=资源ID类型
T this[U id]{get;} //向cache申请使用资源,参数:id=资源ID,返回:资源
Func<U,T> Loader{set;}//回调函数 要求访问者加载资源
Action<U> UnLoader{set;}//回调函数 要求访问者卸载资源
}
[System.Security.Permissions.HostProtectionAttribute(Synchronization=true, ExternalThreading=true)]
public class LRUCache<T,U>:ICache<T,U>,IDisposable where U:IComparable<U>{
Int32 _unUsedSize;//可用资源数量(缓冲区的块数)
Node _head,_tail;//使用带表头的单向链表,_head指向表头,_tail指向表尾,新节点在表尾插入,老节点从表头处摘除,允许将某一个节点移动到表尾
Dictionary<U,Node> _dict;//维护U与Node之间的对照,以提供更高速的Find功能.(ID与Node的前一个节点对应)
test使用
public Int32 _accessed=0,_missed=0;
//构造函数
//参数 size : cache拥有的资源数
// load : 根据资源id加载资源的delegate
// unLoad : 卸载资源的delegate
public LRUCache(Int32 size,Func<U,T> loader,Action<U> unLoader){
_unUsedSize=size;
_tail=_head=new Node(default(U)); //建立表头节点,使用带表头的单向链表存放数据
Loader=loader;
UnLoader=unLoader;
_dict=new Dictionary<U,Node>();
}
//根据id查找对应的节点,返回目标节点的前一个节点
protected Node Find(U id){
//Node p=_head;
//for(;p.Next!=null&&(p.Next.ID.CompareTo(id)!=0);p=p.Next);
//改用dictionary查找表,提高查询效率
return (_dict.ContainsKey(id))?(_dict[id]):_tail;
}
//在队列尾部加入节点
protected Node Append(Node n){
_dict.Add(n.ID,_tail);
return _tail=_tail.InsertAfter(n);
}
//移除一个节点 (n.Next)
protected Node Remove(Node n){
if(n.Next==_tail)
_tail=n;
if(n.Next.Next!=null)
_dict[n.Next.Next.ID]=n;
_dict.Remove(n.Next.ID);
return n.RemoveAfter();
}
//将指定节点的Next移动到队列尾部
protected Node SendBack(Node n){
if(n.Next!=_tail){
return Append(Remove(n));//摘除节点 加到队列尾部
}else
return _tail;
}
//获得一个新的资源。
protected Node CreateResource(U id){
if(_unUsedSize>0){
//有未使用的缓冲区,直接创建管理节点即可
--_unUsedSize;
return new Node(id);
}else{
//从表头摘下一个节点来使用。
Node p=Remove(_head).UnLoadResource(UnLoader);
p.ID=id;
return p;
}
}
protected String Show(Node n){
String s=String.Empty;
for(;n!=null;n=n.Next){
s+=','+n.ID.ToString();
}
return s;
}
~LRUCache(){
Dispose();
}
//实现ICache
public T this[U id]{
get{
lock(_head){
++_accessed;
//Console.WriteLine("+++"+Show(_head));
Node p=Find(id);//查找队列中是否有该资源
if(p.Next==null){
++_missed;
p=Append(CreateResource(id));//无,则创建资源,并加入队列尾部
}else
p=SendBack(p);//有,将资源移到队列尾部
p.LoadResource(Loader);//加载资源(若已经加载,则不做任何动作)
//Console.WriteLine("---"+Show(_head));
return p.Value;
}
}
}
public Func<U,T> Loader{protected get;set;}//回调函数 要求访问者加载资源
public Action<U> UnLoader{protected get;set;}//回调函数 要求访问者卸载资源
//public event CacheEventHandler<T,U> Load;
//public event CacheEventHandler<T,U> UnLoad;
//实现IDisposable
public void Dispose(){
lock(_head){
//卸载cache中的资源
for(Node p=_head.Next;p!=null;p=p.Next)
if(p.Loaded)
p.UnLoadResource(UnLoader);
_head.Next=null;
}
}
//节点定义
public class Node{
public Node(U id){
Value=default(T);
ID=id;
Next=null;
Loaded=false;
}
public T Value{get;set;}
public U ID{get;set;}
public Node Next{get;set;}
public Boolean Loaded{get;set;}
public Node InsertAfter(Node n){
n.Next=this.Next;
this.Next=n;
return n;
}
public Node RemoveAfter(){
Node n=this.Next;
if(n!=null){
this.Next=n.Next;
n.Next=null;
}
return n;
}
//加载资源
public Node LoadResource(Func<U,T> load){
if(!Loaded){
Value=load(ID);
Loaded=true;
}
return this;
}
//卸载资源
public Node UnLoadResource(Action<U> unLoad){
//Console.WriteLine("==="+Show(_head));
if(Loaded){
unLoad(ID);
Value=default(T);
Loaded=false;
}
return this;
}
}
}
public class LIRSCache<T,U>:ICache<T,U>,IDisposable where U:IComparable<U>{
Int32 _grade;//缓冲区级数
Int32[] _unUsedSize;//可用资源数量(缓冲区的块数),_unUsedSize[0]存放cold数据,_unUsedSize[1]存放hot数据
Node[] _head;//使用带表头的单向链表,_head[0]指向表头,_head[1]指向hot区的前一个节点(cold区的最后一个节点),_head[2]指向表尾
Dictionary<U,Node> _dict;//维护U与Node之间的对照,以提供更高速的Find功能.(ID与Node的前一个节点对应)
test使用
public Int32 _accessed=0,_missed=0;
//构造函数
//参数 size : cache拥有的资源数
// load : 根据资源id加载资源的delegate
// unLoad : 卸载资源的delegate
public LIRSCache(Int32 grade,Int32[] size,Func<U,T> loader,Action<U> unLoader){
_grade=grade;
//设置每级缓冲的大小
_unUsedSize=new Int32[_grade];
for(Int32 i=0;i<_grade;++i)
_unUsedSize[i]=size[i];
//初始化缓冲指针//建立表头节点,使用带表头的单向链表存放数据
_head=new Node[_grade+1];
_head[_grade]=new Node(default(U));
for(Int32 i=0;i<_grade;++i)
_head[i]=_head[_grade];
Loader=loader;
UnLoader=unLoader;
_dict=new Dictionary<U,Node>();
}
protected Node Find(U id){
/*foreach(var i in _dict)
Console.WriteLine("{0}->{1}",i.Value.ID,i.Key);*/
/*Node p=_head[0];
for(;p.Next!=null&&(p.Next.ID.CompareTo(id)!=0);p=p.Next);
Node q=(_dict.ContainsKey(id))?(_dict[id]):_head[_grade];
if(p!=q)throw new Exception("Find Error");
return q;*/
//改用dictionary查找表,提高查询效率
return (_dict.ContainsKey(id))?(_dict[id]):_head[_grade];
}
//在队列尾部加入节点
protected Node Append(Int32 selector,Node n){
_dict.Add(n.ID,_head[selector+1]);
if(selector+1<_grade)
_dict[_head[selector+1].Next.ID]=n;
n.Selector=selector;
return _head[selector+1]=_head[selector+1].InsertAfter(n);
}
//移除一个节点 (n.Next)
protected Node Remove(Int32 selector,Node n){
if(n.Next==_head[selector+1])
_head[selector+1]=n;
if(n.Next.Next!=null)
_dict[n.Next.Next.ID]=n;
_dict.Remove(n.Next.ID);
return n.RemoveAfter();
}
//将指定节点的Next移动到队列尾部
protected Node SendBack(Int32 selector,Node n){
if(n.Next!=_head[selector+1])
return Append(selector,Remove(selector,n));//摘除节点 加到队列尾部
else
return _head[selector+1];
}
//获得一个新的资源。
protected Node CreateResource(U id){
//有未使用的缓冲区,直接创建管理节点即可
for(Int32 i=_grade-1;i>=0;--i){
if(_unUsedSize[i]>0){
--_unUsedSize[i];
Node n=new Node(id);
n.Selector=i; //记录新节点的级数
return n;
}
}
//从表头摘下一个节点来使用。
Node p=Remove(0,_head[0]).UnLoadResource(UnLoader);
p.ID=id;
p.Selector=0;//指定为0级节点
return p;
}
protected String Show(Node n){
String s=String.Empty;
for(;n!=null;n=n.Next){
s+=','+n.ID.ToString();
}
return s;
}
~LIRSCache(){
Dispose();
}
//实现ICache
public T this[U id]{
get{
//return default(T);
lock(_head){
++_accessed;
//Console.WriteLine(id);
Node p=Find(id);
Int32 selector;
if(p.Next==null){
//无,则创建资源,并加入队列尾部
++_missed;
p=CreateResource(id);
//队列未满,将节点加入指定级缓冲的尾部
//替换下来的节点,加入0级队列的尾部
Append(p.Selector,p);
}else{
//有,若已经到最后一级,则移动到尾部,否则将资源升级到下一级的头部
selector=p.Next.Selector;
if(selector+1<_grade){
p=Remove(selector,p); // 移除节点
_head[selector+1]=_head[selector+1].Next;//移动selector+1级的头指针,使原先的头节点成为selector级的尾部
_head[selector+1].Selector=selector;//指定节点的新级别
Append(selector+1,p);//在selector+1级尾部添加节点
}else{
p=SendBack(selector,p);
}
}
p.LoadResource(Loader);//加载资源(若已经加载,则不做任何动作)
/*Console.WriteLine("0:"+Show(_head[0]));
Console.WriteLine("1:"+Show(_head[1]));
Console.WriteLine("2:"+Show(_head[2]));*/
return p.Value;
}
}
}
public Func<U,T> Loader{protected get;set;}//回调函数 要求访问者加载资源
public Action<U> UnLoader{protected get;set;}//回调函数 要求访问者卸载资源
//public event CacheEventHandler<T,U> Load;
//public event CacheEventHandler<T,U> UnLoad;
//实现IDisposable
public void Dispose(){
}
public class Node{
public Node(U id){
Value=default(T);
ID=id;
Next=null;
Loaded=false;
Selector=0;
}
public T Value{get;set;}
public U ID{get;set;}
public Int32 Selector{get;set;}
public Node Next{get;set;}
public Boolean Loaded{get;set;}
public Node InsertAfter(Node n){
n.Next=this.Next;
this.Next=n;
return n;
}
public Node RemoveAfter(){
Node n=this.Next;
if(n!=null){
this.Next=n.Next;
n.Next=null;
}
return n;
}
//加载资源
public Node LoadResource(Func<U,T> load){
if(!Loaded){
Value=load(ID);
Loaded=true;
}
return this;
}
//卸载资源
public Node UnLoadResource(Action<U> unLoad){
//Console.WriteLine("==="+Show(_head));
if(Loaded){
unLoad(ID);
Value=default(T);
Loaded=false;
}
return this;
}
}
}
public class Test{
public static void Main(){
List<Int32> list=GetRandomGaussian(1000000);
Stopwatch sw=new Stopwatch ();
sw.Reset();
sw.Start();
TestLRUCache(list);
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
sw.Reset();
sw.Start();
TestLIRSCache(list);
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
sw.Reset();
sw.Start();
TestLIRSCache2(list);
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
sw.Reset();
sw.Start();
TestLIRSCache3(list);
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
}
public static List<Int32> GetRandomList(Int32 len){
List<Int32> list=new List<Int32>();
for(Int32 i=0;i<len;i++)
list.Add(i%1001);
return list;
}
public static List<Int32> GetRandomAvg(Int32 len){
Random r=new Random();
List<Int32> list=new List<Int32>();
for(Int32 i=0;i<len;i++)
list.Add(r.Next(2000));
return list;
}
public static List<Int32> GetRandomGaussian(Int32 len){
GaussianRandom r=new GaussianRandom();
List<Int32> list=new List<Int32>();
for(Int32 i=0;i<len;i++)
list.Add(r.Next(500,250));
return list;
}
public static void TestLIRSCache(List<Int32> r){
LIRSCache<String,Int32> cache =new LIRSCache<String,Int32>(2,new Int32[]{100,900},id=>{return "="+id.ToString()+'=';},id=>{});
Dictionary<Int32,Int32> dict=new Dictionary<Int32,Int32>();
Int32 k;
foreach(Int32 j in r){
if(dict.ContainsKey(j))dict[j]++;else dict.Add(j,1);
//Console.WriteLine(cache[j]);
k=cache[j].Length;
}
Console.WriteLine("LIRS1 : 1-{0}/{1}={2}",cache._missed,cache._accessed,1.0-1.0*cache._missed/cache._accessed);
//dict.OrderBy(d=>d.Key).ForEach(k=>{Console.WriteLine("{0},{1}",k.Key,k.Value);});
}
public static void TestLIRSCache2(List<Int32> r){
LIRSCache<String,Int32> cache =new LIRSCache<String,Int32>(3,new Int32[]{10,100,890},id=>{return "="+id.ToString()+'=';},id=>{});
Dictionary<Int32,Int32> dict=new Dictionary<Int32,Int32>();
Int32 k;
foreach(Int32 j in r){
if(dict.ContainsKey(j))dict[j]++;else dict.Add(j,1);
//Console.WriteLine(cache[j]);
k=cache[j].Length;
}
Console.WriteLine("LIRS2 : 1-{0}/{1}={2}",cache._missed,cache._accessed,1.0-1.0*cache._missed/cache._accessed);
//dict.OrderBy(d=>d.Key).ForEach(k=>{Console.WriteLine("{0},{1}",k.Key,k.Value);});
}
public static void TestLIRSCache3(List<Int32> r){
LIRSCache<String,Int32> cache =new LIRSCache<String,Int32>(2,new Int32[]{10,990},id=>{return "="+id.ToString()+'=';},id=>{});
Dictionary<Int32,Int32> dict=new Dictionary<Int32,Int32>();
Int32 k;
foreach(Int32 j in r){
if(dict.ContainsKey(j))dict[j]++;else dict.Add(j,1);
//Console.WriteLine(cache[j]);
k=cache[j].Length;
}
Console.WriteLine("LIRS3 : 1-{0}/{1}={2}",cache._missed,cache._accessed,1.0-1.0*cache._missed/cache._accessed);
//dict.OrderBy(d=>d.Key).ForEach(k=>{Console.WriteLine("{0},{1}",k.Key,k.Value);});
}
public static void TestLRUCache(List<Int32> r){
LRUCache<String,Int32> cache =new LRUCache<String,Int32>(200,id=>{return "="+id.ToString()+'=';},id=>{});
Dictionary<Int32,Int32> dict=new Dictionary<Int32,Int32>();
Int32 k;
foreach(Int32 j in r){
if(dict.ContainsKey(j))dict[j]++;else dict.Add(j,1);
//Console.WriteLine(cache[j]);
k=cache[j].Length;
}
Console.WriteLine("LRU : 1-{0}/{1}={2}",cache._missed,cache._accessed,1.0-1.0*cache._missed/cache._accessed);
//dict.OrderBy(d=>d.Key).ForEach(k=>{Console.WriteLine("{0},{1}",k.Key,k.Value);});
}
}