lru-caching
Version:
A high-performance LRU caching implementation with TTL support, batch operations and memory optimization
2 lines (1 loc) • 8.02 kB
JavaScript
var lrucaching=(()=>{var m=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var S=Object.prototype.hasOwnProperty;var R=(u,t,e)=>t in u?m(u,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):u[t]=e;var v=(u,t)=>m(u,"name",{value:t,configurable:!0});var w=(u,t)=>{for(var e in t)m(u,e,{get:t[e],enumerable:!0})},A=(u,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of P(t))!S.call(u,n)&&n!==e&&m(u,n,{get:()=>t[n],enumerable:!(s=f(t,n))||s.enumerable});return u};var M=u=>A(m({},"__esModule",{value:!0}),u);var l=(u,t,e)=>R(u,typeof t!="symbol"?t+"":t,e);var z={};w(z,{EntryIndex:()=>I,LinkedList:()=>x,default:()=>T});var I=(i=>(i[i.KEY=0]="KEY",i[i.VALUE=1]="VALUE",i[i.NEXT=2]="NEXT",i[i.PREV=3]="PREV",i[i.EXPIRE=4]="EXPIRE",i))(I||{}),y=class y{constructor(){l(this,"_head");l(this,"_tail");l(this,"_len",0);this._head=["__head_sentinel__",null,null,null,null],this._tail=["__tail_sentinel__",null,null,null,null],this._head[2]=this._tail,this._tail[3]=this._head}insertEntry(t){let e=this._tail,s=e[3];s[2]=t,t[3]=s,t[2]=e,e[3]=t,this._len++}remove(t){let e=t[3],s=t[2];e[2]=s,s[3]=e,t[2]=t[3]=null,this._len--}moveToTail(t){let e=this._tail;if(t[2]===e)return;let s=t[3],n=t[2];s[2]=n,n[3]=s;let i=e[3];i[2]=t,t[3]=i,t[2]=e,e[3]=t}getLRUEntry(){let t=this._head[2];return t!==this._tail?t:null}len(){return this._len}clear(){this._head[2]=this._tail,this._tail[3]=this._head,this._len=0}forEach(t){let e=this._head[2];for(;e!==this._tail;){let s=e[2];t(e),e=s}}};v(y,"LinkedList");var x=y,g=class g{constructor(t){l(this,"_list",new x);l(this,"_maxSize");this._maxSize=t}acquire(){let t=this._list.getLRUEntry();return t?(this._list.remove(t),t):null}release(t){if(this._list.len()>=this._maxSize){let e=this._list.getLRUEntry();e&&this._list.remove(e)}this._list.insertEntry(t)}clear(){this._list.clear()}len(){return this._list.len()}getMaxSize(){return this._maxSize}setMaxSize(t){this._maxSize=t}getList(){return this._list}};v(g,"NodePool");var b=g,h=class h{constructor(t,e=.5,s=3e4,n=5e3,i){l(this,"_list",new x);l(this,"_map",new Map);l(this,"_maxSize");l(this,"_nodePool");l(this,"_poolAdjustInterval");l(this,"_adjustTimer",null);l(this,"_expireCheckInterval");l(this,"_expireTimer",null);l(this,"_onEvict");l(this,"_stats");this._maxSize=Math.max(1,t);let a=Math.max(1,Math.floor(this._maxSize*e));this._nodePool=new b(a),this._poolAdjustInterval=s,this._expireCheckInterval=n,this._onEvict=i,this._stats={get:{batch:0,total:0,hit:0},put:{batch:0,total:0,evict:0,expire:0}},this._startPoolAdjustTimer(),this._startExpireTimer()}_startPoolAdjustTimer(){this._adjustTimer&&clearInterval(this._adjustTimer),this._adjustTimer=setInterval(()=>{let t=this._nodePool.len();if(this._list.len()===0||t===0)return;let s=this._stats.put.total===0?0:Math.min(1,t/this._stats.put.total*10),i=s-.5,a=Math.min(3,Math.max(1,Math.abs(Math.floor(i*5)))),E=this._nodePool.getMaxSize();if(s>.7){let c=Math.min(Math.floor(this._maxSize*.9),E+a);this._nodePool.setMaxSize(c)}else if(s<.3){let c=Math.max(1,E-a);for(this._nodePool.setMaxSize(c);this._nodePool.len()>c;){let o=this._nodePool.getList().getLRUEntry();o&&this._nodePool.getList().remove(o)}}},this._poolAdjustInterval)}_startExpireTimer(){this._expireTimer&&clearInterval(this._expireTimer),this._expireTimer=setInterval(()=>{let t=Date.now(),e=[];this._list.forEach(s=>{let n=s[4];n&&n<t&&e.push(s)}),e.forEach(s=>{let n=s[0],i=s[1];this._list.remove(s),this._map.delete(n),this._onEvict?.(n,i),this._nodePool.release(s),this._stats.put.batch++,this._stats.put.batch>=h.BATCH_SIZE?(this._stats.put.total+=this._stats.put.batch,this._stats.put.expire+=this._stats.put.batch,this._stats.put.batch=0):this._stats.put.expire++}),this._stats.put.batch>0&&(this._stats.put.total+=this._stats.put.batch,this._stats.put.batch=0)},this._expireCheckInterval)}_acquireEntry(t,e,s){this._stats.put.total++;let n=this._nodePool.acquire();return n?(n[0]=e,n[1]=t,n[4]=s?Date.now()+s:null,n):[e,t,null,null,s?Date.now()+s:null]}put(t,e,s){this._stats.put.batch++;let n=this._stats.put.batch>=h.BATCH_SIZE;n&&(this._stats.put.total+=this._stats.put.batch,this._stats.put.batch=0);let i=this._map,a=this._list,E=a._tail,c=null,o=i.get(t);if(o)return o[1]=e,o[4]=s?Date.now()+s:null,o[2]!==E&&a.moveToTail(o),null;if(this._maxSize===1){let r=a.getLRUEntry();r&&(a.remove(r),i.delete(r[0]),c=r[1],this._onEvict?.(r[0],c),this._nodePool.release(r),this._stats.put.evict++)}else if(a.len()>=this._maxSize){let r=a.getLRUEntry();r&&(a.remove(r),i.delete(r[0]),c=r[1],this._onEvict?.(r[0],c),this._nodePool.release(r),this._stats.put.evict++)}let _=this._acquireEntry(e,t,s);return a.insertEntry(_),i.set(t,_),n&&(this._stats.put.evict=Math.floor(this._stats.put.evict/h.BATCH_SIZE)*h.BATCH_SIZE),c}get(t){this._stats.get.batch++;let e=this._stats.get.batch>=h.BATCH_SIZE;e&&(this._stats.get.total+=this._stats.get.batch,this._stats.get.batch=0);let s=this._map.get(t);if(!s){e&&(this._stats.get.total+=this._stats.get.batch,this._stats.get.batch=0);return}let n=Date.now(),i=s[4];if(i&&i<n){this._list.remove(s),this._map.delete(t),this._nodePool.release(s),this._stats.put.expire++,e&&(this._stats.get.total+=this._stats.get.batch,this._stats.get.batch=0);return}return this._stats.get.hit++,this._list.moveToTail(s),e&&(this._stats.get.total+=this._stats.get.batch,this._stats.get.hit=Math.floor(this._stats.get.hit/h.BATCH_SIZE)*h.BATCH_SIZE,this._stats.get.batch=0),s[1]}delete(t){let e=this._map.get(t);if(!e)return null;this._list.remove(e),this._map.delete(t);let s=e[1];return this._onEvict?.(t,s),this._nodePool.release(e),s}batchPut(t){let e=[],s=this._map,n=this._list,i=this._maxSize,a=Date.now();t.forEach(o=>{let{key:_,value:r,ttl:p}=o,d=s.get(_);d&&(d[1]=r,d[4]=p?a+p:null,d[2]!==n._tail&&n.moveToTail(d))});let E=t.filter(o=>!s.has(o.key));if(E.length===0)return e;let c=Math.max(0,E.length+n.len()-i);if(c>0){let o=0,_=n.getLRUEntry();for(;_&&o<c;){let r=_[2],p=_[0],d=_[1];n.remove(_),s.delete(p),this._onEvict?.(p,d),this._nodePool.release(_),e.push(d),this._stats.put.evict++,o++,_=r!==n._tail?r:null}}return E.forEach(o=>{let{key:_,value:r,ttl:p}=o,d=this._acquireEntry(r,_,p);n.insertEntry(d),s.set(_,d)}),this._stats.put.batch+=t.length,this._stats.put.batch>=h.BATCH_SIZE&&(this._stats.put.total+=this._stats.put.batch,this._stats.put.evict=Math.floor(this._stats.put.evict/h.BATCH_SIZE)*h.BATCH_SIZE,this._stats.put.batch=0),e}batchDelete(t){let e=[],s=this._map,n=this._list;return t.forEach(i=>{let a=s.get(i);if(a){n.remove(a),s.delete(i);let E=a[1];e.push(E),this._onEvict?.(i,E),this._nodePool.release(a)}}),e}forEach(t){this._list.forEach(e=>{let s=e[0],n=e[1],i=e[4];t(s,n,i||void 0)})}clear(){this._list.forEach(t=>{this._nodePool.len()<this._nodePool.getMaxSize()&&this._nodePool.release(t)}),this._list.clear(),this._map.clear(),this._nodePool.clear(),this._stats={get:{batch:0,total:0,hit:0},put:{batch:0,total:0,evict:0,expire:0}}}len(){return this._list.len()}has(t){let e=this._map.get(t);if(!e)return!1;let s=e[4];return!s||s>=Date.now()}getStats(){let t=this._stats.get.total+this._stats.get.batch,e=this._stats.get.hit+(this._stats.get.batch>0?Math.min(this._stats.get.batch,this._stats.get.hit%h.BATCH_SIZE):0),s=this._stats.put.total+this._stats.put.batch,n=this._stats.put.evict+(this._stats.put.batch>0?Math.min(this._stats.put.batch,this._stats.put.evict%h.BATCH_SIZE):0),i=this._stats.put.expire+(this._stats.put.batch>0?Math.min(this._stats.put.batch,this._stats.put.expire%h.BATCH_SIZE):0);return{get:{total:t,hit:e,hitRate:t===0?0:Math.round(e/t*1e4)/100},put:{total:s,evict:n,expire:i},pool:{size:this._nodePool.len(),maxSize:this._nodePool.getMaxSize()}}}dispose(){this._adjustTimer&&(clearInterval(this._adjustTimer),this._adjustTimer=null),this._expireTimer&&(clearInterval(this._expireTimer),this._expireTimer=null),this.clear(),this._list=null,this._map=null,this._nodePool=null,this._onEvict=void 0}};v(h,"LRU"),l(h,"BATCH_SIZE",100);var T=h;return M(z);})();