UNPKG

transitory

Version:

In-memory cache with high hit rates via LFU eviction. Supports time-based expiration, automatic loading and metrics.

110 lines 3.34 kB
import { hashcode } from './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) { let 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. */ export class CountMinSketch { constructor(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); } findIndex(h1, h2, d) { const h = h1 + safeishMultiply(h2, d); return (d * this.width) + (h & (this.width - 1)); } update(hashCode) { const table = this.table; const maxSize = this.maxSize; const estimate = this.estimate(hashCode); const h2 = hash2(hashCode); let added = false; for (let i = 0, n = this.depth; i < n; i++) { const idx = this.findIndex(hashCode, h2, i); const v = table[idx]; if (v + 1 < maxSize && v <= estimate) { table[idx] = v + 1; added = true; } } if (added && ++this.additions === this.resetAfter) { this.performReset(); } } estimate(hashCode) { const table = this.table; const h2 = hash2(hashCode); let result = this.maxSize; for (let i = 0, n = this.depth; i < n; i++) { const value = table[this.findIndex(hashCode, h2, i)]; if (value < result) { result = value; } } return result; } performReset() { const table = this.table; this.additions /= 2; for (let i = 0, n = table.length; i < n; i++) { this.additions -= table[i] & 1; table[i] = Math.floor(table[i] >>> 1); } } static hash(key) { return hashcode(key); } static uint8(width, depth, decay = true) { return new CountMinSketch(width, depth, decay); } } //# sourceMappingURL=CountMinSketch.js.map