transitory
Version:
In-memory cache with high hit rates via LFU eviction. Supports time-based expiration, automatic loading and metrics.
116 lines • 3.79 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CountMinSketch = void 0;
var hashcode_1 = require("./hashcode");
/**
* Helper function to calculate the closest power of two to N.
*
* @param n -
* input
* @returns
* closest power of two to `n`
*/
function toPowerOfN(n) {
return Math.pow(2, Math.ceil(Math.log(n) / Math.LN2));
}
/**
* Calculates a component of the hash.
*
* @param a0 -
* @returns
* hash
*/
function hash2(a0) {
var a = (a0 ^ 61) ^ (a0 >>> 16);
a = a + (a << 3);
a = a ^ (a >>> 4);
a = safeishMultiply(a, 0x27d4eb2d);
a = a ^ (a >>> 15);
return a;
}
/**
* Tiny helper to perform a multiply that's slightly safer to use for hashing.
*
* @param a -
* @param b -
* @returns a * b
*/
function safeishMultiply(a, b) {
return ((a & 0xffff) * b) + ((((a >>> 16) * b) & 0xffff) << 16);
}
/**
* Count-min sketch suitable for use with W-TinyLFU. Similiar to a regular
* count-min sketch but with a few important differences to achieve better
* estimations:
*
* 1) Enforces that the width of the sketch is a power of 2.
* 2) Uses a reset that decays all values by half when width * 10 additions
* have been made.
*/
var CountMinSketch = /** @class */ (function () {
function CountMinSketch(width, depth, decay) {
this.width = toPowerOfN(width);
this.depth = depth;
// Get the maximum size of values, assuming unsigned ints
this.maxSize = Math.pow(2, Uint8Array.BYTES_PER_ELEMENT * 8) - 1;
this.halfMaxSize = this.maxSize / 2;
this.slightlyLessThanHalfMaxSize = this.halfMaxSize - Math.max(this.halfMaxSize / 4, 1);
// Track additions and when to reset
this.additions = 0;
this.resetAfter = decay ? width * 10 : -1;
// Create the table to store data in
this.table = new Uint8Array(this.width * depth);
}
CountMinSketch.prototype.findIndex = function (h1, h2, d) {
var h = h1 + safeishMultiply(h2, d);
return (d * this.width) + (h & (this.width - 1));
};
CountMinSketch.prototype.update = function (hashCode) {
var table = this.table;
var maxSize = this.maxSize;
var estimate = this.estimate(hashCode);
var h2 = hash2(hashCode);
var added = false;
for (var i = 0, n = this.depth; i < n; i++) {
var idx = this.findIndex(hashCode, h2, i);
var v = table[idx];
if (v + 1 < maxSize && v <= estimate) {
table[idx] = v + 1;
added = true;
}
}
if (added && ++this.additions === this.resetAfter) {
this.performReset();
}
};
CountMinSketch.prototype.estimate = function (hashCode) {
var table = this.table;
var h2 = hash2(hashCode);
var result = this.maxSize;
for (var i = 0, n = this.depth; i < n; i++) {
var value = table[this.findIndex(hashCode, h2, i)];
if (value < result) {
result = value;
}
}
return result;
};
CountMinSketch.prototype.performReset = function () {
var table = this.table;
this.additions /= 2;
for (var i = 0, n = table.length; i < n; i++) {
this.additions -= table[i] & 1;
table[i] = Math.floor(table[i] >>> 1);
}
};
CountMinSketch.hash = function (key) {
return (0, hashcode_1.hashcode)(key);
};
CountMinSketch.uint8 = function (width, depth, decay) {
if (decay === void 0) { decay = true; }
return new CountMinSketch(width, depth, decay);
};
return CountMinSketch;
}());
exports.CountMinSketch = CountMinSketch;
//# sourceMappingURL=CountMinSketch.js.map