Skip to content

Commit

Permalink
feat: Benchmarking caches (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrflip committed Aug 7, 2022
1 parent 839c3cf commit 5e5bd95
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 7 deletions.
96 changes: 96 additions & 0 deletions benchmark/lru-cache/key-distributions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
var randomString = require('pandemonium/random-string');
var random = require('pandemonium/random');
var typed = require('../../utils/typed-arrays.js');
var {snipToLast} = require('../../utils/snip.js');

module.exports.random = random;
module.exports.randomString = randomString;

function randArr(size, range, rng) {
var ValArrayFactory = typed.getPointerArray(range);
var arr = new ValArrayFactory(size)
for (var ii = 0; ii < size; ii++) {
arr[ii] = rng(ii);
}
return arr
}
module.exports.randArr = randArr;

function longTailArr(size, range, power) {
var intgen = longTailIntGen(range, power)
return randArr(size, range, intgen);
}
module.exports.longTailArr = longTailArr;

function flatDistArr(size, range) {
var intgen = () => random(0, range - 1);
return randArr(size, range, intgen);
}
module.exports.flatDistArr = flatDistArr;

function ascendingArr(size, range) {
var intgen = (ii) => (ii);
return randArr(size, range, intgen);
}
module.exports.ascendingArr = ascendingArr;

function longTailIntGen(range, power = -0.8) {
return function intgen() {
var rand = Math.random()
var yy = (1 - rand)**(power) - 1
var result = Math.floor(0.25 * range * yy)
if (result < range) { return result }
return intgen()
}
}
module.exports.longTailIntGen = longTailIntGen;

function longTailStrGen(range, power = -0.8) {
var intgen = longTailIntGen(range, power);
return function strgen() {
return String(intgen())
}
}
module.exports.longTailStrGen = longTailStrGen;

function stringifyArr(arr) {
var stringArr = [];
for (var ii = 0; ii < arr.length; ii++) {
stringArr.push(arr[ii]);
}
return stringArr;
}
module.exports.stringifyArr = stringifyArr;

function comparePairTails([kk1, vv1], [kk2, vv2]) {
if (vv2 > vv1) { return 1 }
if (vv2 < vv1) { return -1 }
if (kk2 > kk1) { return -1 }
if (kk2 < kk1) { return 1 }
return 1
}

function showDistribution(arr, chunk = 1) {
var counts = new Map();
for (var item of arr) {
bin = chunk * Math.floor(item / chunk)
if (! counts.has(bin)) { counts.set(bin, 0); }
counts.set(bin, 1 + counts.get(bin));
}
var entries = [...counts].sort(comparePairTails)
var histo = new Map(entries)
histo.last = entries[entries.length - 1]
return histo
}

function examineDist(keys) {
var histA = showDistribution(keys, 1)
var histB = showDistribution(keys, 10000)
console.log(
oops,
keys.length,
histA.size,
snipToLast(histA.entries(), new Map(), {maxToDump: 25, last: histA.last, size: histA.size}),
histB,
)
}
145 changes: 145 additions & 0 deletions benchmark/lru-cache/performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
var random = require('pandemonium/random');
var Benchmark = require('benchmark')
var Keymaster = require('./key-distributions.js');
var LRUCache = require('../../lru-cache.js'),
LRUMap = require('../../lru-map.js'),
LRUCacheWithDelete = require('../../lru-cache-with-delete.js'),
LRUMapWithDelete = require('../../lru-map-with-delete.js');

var TEST_CAP = 30000

// 400k entries with approx 42k distinct values btwn 0 and 60k, distributed 300k/65k/23k/10k/5k/3k (~97% in the top 30k)
var NumKeys97 = Keymaster.longTailArr(400000, 60000, -0.4);
var StrKeys97 = Keymaster.stringifyArr(NumKeys97);
var intgen97 = Keymaster.longTailIntGen(60000, -0.4);
var strgen97 = Keymaster.longTailStrGen(60000, -0.4);
NumKeys97.note = 'Long-tail pool of 42,000 distinct values, 97% in the top 30k, 75% in the top 10k'; StrKeys97.note = NumKeys97.note;
// 400k entries with approx 50k distinct values btwn 0 and 60k, distributed 230k/80k/40k/22k/15k/10k (~88% in the top 30k)
var NumKeys88 = Keymaster.longTailArr(400000, 60000, -0.7)
// 400k entries with approx 60k distinct values btwn 0 and 60k, distributed 135k/85k/61k/48k/39k/33k (~70% in the top 30k)
var NumKeys70 = Keymaster.longTailArr(400000, 60000, -10);
var StrKeys70 = Keymaster.stringifyArr(NumKeys70);
NumKeys70.note = 'Long-tail pool of ~60,000 distinct values, 70% in the top 30k, 33% in the top 10k'; StrKeys70.note = NumKeys70.note;
// 120k entries with approx 52k distinct values btwn 0 and 60k, distributed evenly
var NumKeysFlat = Keymaster.flatDistArr(120000, 60000);
var StrKeysFlat = Keymaster.stringifyArr(NumKeysFlat);
// 31k entries running 0-31k in order
var NumKeysOrdered = Keymaster.ascendingArr(31000, 31000);
var StrKeysOrdered = Keymaster.stringifyArr(NumKeysOrdered);

const CACHES = [LRUCache, LRUCacheWithDelete, LRUMap, LRUMapWithDelete, LRUCache];

// var emptyCaches = makeCaches();
// scenario(emptyCaches, (cache) => (function() { readAll(cache, StrKeys97); }));

var fullCaches = makeLoadedCaches(StrKeysOrdered);
fullCaches.note = 'Pre-loaded 30k capacity caches';

// console.log(`Pre-loaded 30k capacity caches, 400k reps of
// one write from equally-occurring pool of 60k distinct keys, then
// one read from a long-tail pool of 60k distinct keys (70% in 30k, 33% in 10k)`);
scenario('1x flat writes, 1x gentle spread read', fullCaches, (cache) => (function() {
write1Read1(cache, [StrKeysFlat, StrKeys70], StrKeys70.length);
}));

console.log(`Pre-loaded 30k capacity caches, 400k reps of
one write from equally-occurring pool of 60k distinct keys,
then four reads from a long-tail pool of 60k distinct keys (70% in 30k, 33% in 10k)`);
scenario('1x flat writes, 4x gentle spread read', fullCaches, (cache) => (function() {
write1Read4(cache, [StrKeysFlat, StrKeys70], StrKeys70.length);
}));

console.log(`${fullCaches.note}\nRead one value then write one value from the same sharp (97/70) distribution`);
scenario('individual get then set, sharp spread', fullCaches, (cache) => (function() {
cache.get(strgen97());
cache.set(strgen97(), 'hi');
}));

console.log(`${fullCaches.note}\nRead one value then write one value from a flat distribution 33% larger than the cache`);
scenario('individual get then set, flat spread', fullCaches, (cache) => (function() {
cache.get(String(random(0, 40000)));
cache.set(String(random(0, 40000)), 'hi');
}));

console.log(`Pre-loaded 30k capacity caches, random-order reads (no writes) of 42,000 distinct values.
The top 30k of values occur ~97% of the time and the top 10k values 75% of the time.`);
//
scenario('read-only sharp spread', fullCaches, (cache) => (function() { readAll(cache, StrKeys97); }));

console.log(`Pre-loaded 30k capacity caches, random-order reads (no writes) of 60,000 distinct values.
The top 30k of values occur ~70% of the time and the top 10k values 33% of the time.`);
//
scenario('read-only gentle spread', fullCaches, (cache) => (function() { readAll(cache, StrKeys70); }));

function readAll(cache, arrA, count) {
if (! count) { count = arrA.length; }
for (var ii = 0; ii < count; ii++) {
cache.get(arrA[ii % arrA.length])
}
}

function writeAll(cache, arrA, count) {
if (! count) { count = arrA.length; }
for (var ii = 0; ii < count; ii++) {
var storeme = arrA[ii % arrA.length]
cache.set(storeme, storeme)
}
}

function write1Read1(cache, [arrA, arrB], count) {
var blen = arrB.length;
if (! count) { count = arrA.length; }
for (var ii = 0; ii < count; ii++) {
var storeme = arrA[ii % arrA.length]
cache.set(storeme, storeme)
cache.get(arrB[ii % blen])
}
}

function write1Read4(cache, [arrA, arrB], count) {
var blen = arrB.length;
var boff0 = 0, boff1 = blen * 0.25, boff2 = blen * 0.50, boff1 = blen * 0.75;
if (! count) { count = arrA.length; }
for (var ii = 0; ii < count; ii++) {
var storeme = arrA[ii % arrA.length]
cache.set(storeme, storeme)
cache.get(arrB[(ii + boff0) % blen])
cache.get(arrB[(ii + boff1) % blen])
cache.get(arrB[(ii + boff2) % blen])
cache.get(arrB[(ii + boff3) % blen])
}
}

function decoratedSuite(name) {
return new Benchmark.Suite('Testing caches')
.on('cycle', event => {
const benchmark = event.target;
console.log(benchmark.toString());
}) // .on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); });
}

function makeCaches(factories = CACHES) {
return factories.map((CacheFactory) => {
var cache = new CacheFactory(TEST_CAP);
cache.name = CacheFactory.name;
return cache;
})
}

function makeLoadedCaches(arrA, count) {
if (! count) { count = arrA.length; }
var caches = makeCaches()
caches.forEach((cache) => {
writeAll(cache, arrA, count);
})
return caches;
}

function scenario(act, caches, actionsFactory, info) {
var suite = decoratedSuite(act);
caches.forEach((cache) => {
var actions = actionsFactory(cache, info);
suite.add(`${act} ${cache.name}`, actions);
})
suite.run();
}
1 change: 1 addition & 0 deletions lru-cache-with-delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ if (typeof Symbol !== 'undefined') {
});
LRUCacheWithDelete.prototype[Symbol.for('nodejs.util.inspect.custom')] = LRUCache.prototype.inspect;
}
Object.defineProperty(LRUCacheWithDelete.prototype, 'summary', Object.getOwnPropertyDescriptor(LRUCache.prototype, 'summary'));

/**
* Method used to clear the structure.
Expand Down
8 changes: 4 additions & 4 deletions lru-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,17 +372,17 @@ LRUCache.prototype.entries = function() {
/**
* Return a short string for interpolation: `LRUCache:size/capacity`
*/
LRUCache.prototype.summaryString = function summaryString() {
return `${this.constructor.name}:${this.size}/${this.capacity}`;
};
Object.defineProperty(LRUCache.prototype, 'summary', {
get: function() { return `${this.constructor.name}:${this.size}/${this.capacity}` },
});

/**
* Attaching the #.entries method to Symbol.iterator if possible.
*/
if (typeof Symbol !== 'undefined') {
LRUCache.prototype[Symbol.iterator] = LRUCache.prototype.entries;
Object.defineProperty(LRUCache.prototype, Symbol.toStringTag, {
get: function () { return this.summaryString(); },
get: function () { return this.summary; },
});
}

Expand Down
1 change: 1 addition & 0 deletions lru-map-with-delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ if (typeof Symbol !== 'undefined') {
});
LRUMapWithDelete.prototype[Symbol.for('nodejs.util.inspect.custom')] = LRUMap.prototype.inspect;
}
Object.defineProperty(LRUMapWithDelete.prototype, 'summary', Object.getOwnPropertyDescriptor(LRUMap.prototype, 'summary'));

/**
* Method used to clear the structure.
Expand Down
1 change: 1 addition & 0 deletions lru-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ if (typeof Symbol !== 'undefined') {
});
LRUMap.prototype[Symbol.for('nodejs.util.inspect.custom')] = LRUCache.prototype.inspect;
}
Object.defineProperty(LRUMap.prototype, 'summary', Object.getOwnPropertyDescriptor(LRUCache.prototype, 'summary'));

/**
* Static @.from function taking an arbitrary iterable & converting it into
Expand Down
Loading

0 comments on commit 5e5bd95

Please sign in to comment.