(Это просто длинный комментарий)
Вот кеш часов (который является приближением LRU), который работает только со строками / целыми числами в качестве ключей и имеет только метод get:
O (1) попадание в кеш, нет движения узла (дружественный сборщик мусора)
O (1) промах в кэше, асинхронный
количество асинхронных средств доступа (или асинхронных промахов кеша) должно быть равно или меньше размера кеша, в противном случае возникает временная тупиковая блокировка
lrucache.js:
'use strict';
/*
cacheSize: number of elements in cache, constant, must be greater than or equal to number of asynchronous accessors / cache misses
callbackBackingStoreLoad: user-given cache-miss function to load data from datastore
elementLifeTimeMs: maximum miliseconds before an element is invalidated, only invalidated at next get() call with its key
*/
let Lru = function(cacheSize,callbackBackingStoreLoad,elementLifeTimeMs=1000){
const me = this;
const maxWait = elementLifeTimeMs;
const size = parseInt(cacheSize,10);
const mapping = {};
const mappingInFlightMiss = {};
const bufData = new Array(size);
const bufVisited = new Uint8Array(size);
const bufKey = new Array(size);
const bufTime = new Float64Array(size);
const bufLocked = new Uint8Array(size);
for(let i=0;i<size;i++)
{
let rnd = Math.random();
mapping[rnd] = i;
bufData[i]="";
bufVisited[i]=0;
bufKey[i]=rnd;
bufTime[i]=0;
bufLocked[i]=0;
}
let ctr = 0;
let ctrEvict = parseInt(cacheSize/2,10);
const loadData = callbackBackingStoreLoad;
let inFlightMissCtr = 0;
this.get = function(keyPrm,callbackPrm){
const key = keyPrm;
const callback = callbackPrm;
// stop dead-lock when many async get calls are made
if(inFlightMissCtr>=size)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
return;
}
// delay the request towards end of the cache-miss completion
if(key in mappingInFlightMiss)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
return;
}
if(key in mapping)
{
let slot = mapping[key];
// RAM speed data
if((Date.now() - bufTime[slot]) > maxWait)
{
if(bufLocked[slot])
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
}
else
{
delete mapping[key];
me.get(key,function(newData){
callback(newData);
});
}
}
else
{
bufVisited[slot]=1;
bufTime[slot] = Date.now();
callback(bufData[slot]);
}
}
else
{
// datastore loading + cache eviction
let ctrFound = -1;
while(ctrFound===-1)
{
// give slot a second chance before eviction
if(!bufLocked[ctr] && bufVisited[ctr])
{
bufVisited[ctr]=0;
}
ctr++;
if(ctr >= size)
{
ctr=0;
}
// eviction conditions
if(!bufLocked[ctrEvict] && !bufVisited[ctrEvict])
{
// evict
bufLocked[ctrEvict] = 1;
inFlightMissCtr++;
ctrFound = ctrEvict;
}
ctrEvict++;
if(ctrEvict >= size)
{
ctrEvict=0;
}
}
mappingInFlightMiss[key]=1;
let f = function(res){
delete mapping[bufKey[ctrFound]];
bufData[ctrFound]=res;
bufVisited[ctrFound]=0;
bufKey[ctrFound]=key;
bufTime[ctrFound]=Date.now();
bufLocked[ctrFound]=0;
mapping[key] = ctrFound;
callback(bufData[ctrFound]);
inFlightMissCtr--;
delete mappingInFlightMiss[key];
};
loadData(key,f);
}
};
};
exports.Lru = Lru;
Аннулирование на основе времени происходит только во время операции cache.get (), поэтому он не использует обработчик событий для каких-либо элементов.
Пример использования:
let Lru = require("./lrucache.js").Lru;
let num_cache_elements = 1000;
let element_life_time_miliseconds = 1000;
let cache = new Lru(num_cache_elements, async function(key,callback){
// datastore access for filling the missing cache element when user access key
callback(some_time_taking_io_work_or_heavy_computation(key));
}, element_life_time_miliseconds);
cache.get("some_key_string",function(data){
// data comes from datastore or RAM depending on its lifetime left or the key acceess pattern
// do_something_with(data);
});
Пример кеширования файлов:
let Lru = require("./lrucache.js").Lru;
let fs = require("fs");
let path = require("path");
let fileCache = new Lru(500, async function(key,callback){
// cache-miss data-load algorithm
fs.readFile(path.join(__dirname,key),function(err,data){
if(err) {
callback({stat:404, data:JSON.stringify(err)});
}
else
{
callback({stat:200, data:data});
}
});
},1000 /* cache element lifetime */);
// test with a file
// cache-miss
let t1 = Date.now();
fileCache.get("./test.js",function(dat){
console.log("Cache-miss time:");
console.log(Date.now()-t1);
console.log("file data:");
console.log(dat.data.length+" bytes");
// cache-hit
t1 = Date.now();
fileCache.get("./test.js",function(dat){
console.log("Cache-hit time:");
console.log(Date.now()-t1);
console.log("file data:");
console.log(dat.data.length+" bytes");
});
});
Тест асинхронного пропускания кэша:
let Lru = require("./lrucache.js").Lru;
let benchSize = 500;
let cacheMiss= 0;
let cacheAccess= 0;
let cache = new Lru(900, async function(key,callback){
// cache-miss data-load algorithm
setTimeout(function(){
cacheMiss++
callback(key+" processed");
},1000);
},1000 /* cache element lifetime */);
let ctrBench = 0;
function bench()
{
let ctr = 0;
let t1 = Date.now();
for(let i=0;i<benchSize;i++)
{
let key = parseInt(Math.random()*1000,10);
cache.get(key,function(data){
cacheAccess++;
if(key.toString()+" processed" !== data)
{
console.log("error: wrong key-data mapping.");
}
if(++ctr === benchSize)
{
console.log("benchmark: "+(Date.now()-t1)+" miliseconds");
console.log("cache hit: "+(cacheAccess-cacheMiss));
console.log("cache miss: "+(cacheMiss));
if(ctrBench<20)
{
ctrBench++
bench();
}
}
});
}
}
bench();
вывод:
benchmark: 1034 miliseconds
cache hit: 125
cache miss: 375
benchmark: 1015 miliseconds
cache hit: 374
cache miss: 626
benchmark: 1017 miliseconds
cache hit: 571
cache miss: 929
benchmark: 1013 miliseconds
cache hit: 750
cache miss: 1250
benchmark: 1016 miliseconds
cache hit: 950
cache miss: 1550
benchmark: 1017 miliseconds
cache hit: 1132
cache miss: 1868
benchmark: 1013 miliseconds
cache hit: 1324
cache miss: 2176
benchmark: 1014 miliseconds
cache hit: 1485
cache miss: 2515
benchmark: 1016 miliseconds
cache hit: 1675
cache miss: 2825
benchmark: 1016 miliseconds
cache hit: 1872
cache miss: 3128
benchmark: 1012 miliseconds
cache hit: 2053
cache miss: 3447
benchmark: 1015 miliseconds
cache hit: 2224
cache miss: 3776
benchmark: 1015 miliseconds
cache hit: 2405
cache miss: 4095
benchmark: 1016 miliseconds
cache hit: 2580
cache miss: 4420
benchmark: 1015 miliseconds
cache hit: 2760
cache miss: 4740
benchmark: 1013 miliseconds
cache hit: 2930
cache miss: 5070
benchmark: 1047 miliseconds
cache hit: 3105
cache miss: 5395
benchmark: 1015 miliseconds
cache hit: 3280
cache miss: 5720
benchmark: 1012 miliseconds
cache hit: 3449
cache miss: 6051
benchmark: 1015 miliseconds
cache hit: 3636
cache miss: 6364
benchmark: 1016 miliseconds
cache hit: 3818
cache miss: 6682
Срок службы 1000 мс здесь снижает коэффициент попадания в кеш. В противном случае ожидается 90% -ный коэффициент попадания.
person
huseyin tugrul buyukisik
schedule
05.04.2021