123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105openEntry(* Clock-eviction cache
A circular array of slots sits beside the hashtable.
* Each slot carries the key currently occupying it and
* a reference bit that is set on every hit and
* cleared by the clock hand on its first pass.
The hand evicts the first slot whose reference bit is already clear,
giving every recently-accessed entry at least one full revolution of grace. *)moduleMake(Entry:ENTRY)=structmoduleTable=Hashtbl.Make(Entry)typeslot={mutablekey:Entry.t;mutableref:bool}type'at={table:('a*int)Table.t;clock:slotarray;occ:boolarray;(* true iff slot is occupied *)capacity:int;mutablecount:int;(* number of occupied slots *)mutablehand:int;(* eviction hand position *)mutablefill:int;(* next slot for sequential initial fill *)mutabletouched:int;(* one past the largest slot index ever written *)}letcreate~(size:int)=letcapacity=max1sizein{table=Table.createcapacity;clock=Array.initcapacity(fun_->{key=Entry.default;ref=false});occ=Array.makecapacityfalse;capacity;count=0;hand=0;fill=0;touched=0;}letsize(cache:'at):int=cache.count(* Remove every entry, visiting only the slots that were written *)letempty(cache:'at):unit=foridx=0tocache.touched-1doifcache.occ.(idx)then(Table.removecache.tablecache.clock.(idx).key;cache.occ.(idx)<-false;cache.clock.(idx).key<-Entry.default;cache.clock.(idx).ref<-false)done;cache.count<-0;cache.hand<-0;cache.fill<-0;cache.touched<-0letfind(cache:'at)(key:Entry.t):'aoption=matchTable.find_optcache.tablekeywith|None->None|Some(value,idx)->cache.clock.(idx).ref<-true;Somevalue(* Advance the hand until a slot can be evicted; return its index *)letevict(cache:'at):int=letcapacity=cache.capacityinletrecsweep()=letidx=cache.handincache.hand<-(idx+1)modcapacity;ifnotcache.occ.(idx)thensweep()elseifcache.clock.(idx).refthen(cache.clock.(idx).ref<-false;sweep())else(Table.removecache.tablecache.clock.(idx).key;cache.occ.(idx)<-false;cache.count<-cache.count-1;idx)insweep()letadd(cache:'at)(key:Entry.t)(value:'a):unit=matchTable.find_optcache.tablekeywith|Some(_,idx)->Table.replacecache.tablekey(value,idx);cache.clock.(idx).ref<-true|None->letidx=ifcache.count<cache.capacitythen(letidx=cache.fillincache.fill<-(cache.fill+1)modcache.capacity;ifidx+1>cache.touchedthencache.touched<-idx+1;idx)elseevictcacheincache.clock.(idx).key<-key;cache.clock.(idx).ref<-true;cache.occ.(idx)<-true;Table.replacecache.tablekey(value,idx);cache.count<-cache.count+1end