From bf52318f95428fe0a79d51d5bd211b6ba00b6c9b Mon Sep 17 00:00:00 2001 From: falsandtru Date: Wed, 28 Feb 2024 04:15:24 +0900 Subject: [PATCH] Refactoring --- benchmark/cache.ts | 523 ++++++++++++++++++++++----------------------- src/cache.test.ts | 46 +++- src/cache.ts | 51 ++--- 3 files changed, 326 insertions(+), 294 deletions(-) diff --git a/benchmark/cache.ts b/benchmark/cache.ts index 54ae5184..755abafc 100644 --- a/benchmark/cache.ts +++ b/benchmark/cache.ts @@ -8,7 +8,7 @@ import { LRUCache } from 'lru-cache'; import { xorshift } from '../src/random'; import { captureTimers } from '../src/timer'; -describe('Benchmark:', function () { +describe.only('Benchmark:', function () { describe('Cache', function () { it('Clock new', function (done) { benchmark('Clock new', () => new Clock(10000), done); @@ -34,413 +34,410 @@ describe('Benchmark:', function () { benchmark('DWC new', () => new Cache(10000), done); }); - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - it(`Clock set miss ${size.toLocaleString('en')}`, function (done) { - const cache = new Clock(size); - for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - let i = 0; - benchmark(`Clock set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, {}), done); - }); - - it(`ILRU set miss ${size.toLocaleString('en')}`, function (done) { - const cache = new LRUCache({ max: size }); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - let i = 0; - benchmark(`ILRU set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, {}), done); - }); - - it(`LRU set miss ${size.toLocaleString('en')}`, function (done) { - const cache = new LRU(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - let i = 0; - benchmark(`LRU set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, {}), done); - }); - - it(`TRC-C set miss ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCC(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - let i = 0; - benchmark(`TRC-C set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, {}), done); - }); - - it(`TRC-L set miss ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCL(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - let i = 0; - benchmark(`TRC-L set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, {}), done); - }); - - it(`DWC set miss ${size.toLocaleString('en')}`, function (done) { - const cache = new Cache(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - let i = 0; - benchmark(`DWC set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, {}), done); - }); - } - - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - it(`Clock set hit ${size.toLocaleString('en')}`, function (done) { - const cache = new Clock(size); - for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`Clock set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, {}), done); - }); - - it(`ILRU set hit ${size.toLocaleString('en')}`, function (done) { - const cache = new LRUCache({ max: size }); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`ILRU set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, {}), done); - }); - - it(`LRU set hit ${size.toLocaleString('en')}`, function (done) { - const cache = new LRU(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`LRU set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, {}), done); - }); - - it(`TRC-C set hit ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCC(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`TRC-C set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, {}), done); - }); - - it(`TRC-L set hit ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCL(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`TRC-L set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, {}), done); - }); - - it(`DWC set hit ${size.toLocaleString('en')}`, function (done) { - const cache = new Cache(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`DWC set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, {}), done); - }); - } - - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - it(`Clock get miss ${size.toLocaleString('en')}`, function (done) { - const cache = new Clock(size); - for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - benchmark(`Clock get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`ILRU get miss ${size.toLocaleString('en')}`, function (done) { - const cache = new LRUCache({ max: size }); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - benchmark(`ILRU get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`LRU get miss ${size.toLocaleString('en')}`, function (done) { - const cache = new LRU(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - benchmark(`LRU get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`TRC-C get miss ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCC(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - benchmark(`TRC-C get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`TRC-L get miss ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCL(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - benchmark(`TRC-L get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`DWC get miss ${size.toLocaleString('en')}`, function (done) { - const cache = new Cache(size); - for (let i = 0; i < size; ++i) cache.set(~i, {}); - const random = xorshift.random(1); - benchmark(`DWC get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - } - - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - it(`Clock get hit ${size.toLocaleString('en')}`, function (done) { - const cache = new Clock(size); - for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`Clock get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`ILRU get hit ${size.toLocaleString('en')}`, function (done) { - const cache = new LRUCache({ max: size }); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`ILRU get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`LRU get hit ${size.toLocaleString('en')}`, function (done) { - const cache = new LRU(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`LRU get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`TRC-C get hit ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCC(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`TRC-C get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`TRC-L get hit ${size.toLocaleString('en')}`, function (done) { - const cache = new TRCL(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`TRC-L get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - - it(`DWC get hit ${size.toLocaleString('en')}`, function (done) { - const cache = new Cache(size); - for (let i = 0; i < size; ++i) cache.set(i, {}); - const random = xorshift.random(1); - benchmark(`DWC get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); - }); - } - - // 1e7はシミュだけ実行するとISCが単体でもGitHub Actionsの次の環境とエラーで落ちる。 - // ベンチ全体を実行したときはなぜか落ちない。 - // - // Error: Uncaught RangeError: Map maximum size exceeded (dist/index.js:16418) - // - // System: - // OS: Linux 5.15 Ubuntu 20.04.5 LTS (Focal Fossa) - // CPU: (2) x64 Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz - // Memory: 5.88 GB / 6.78 GB - // - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - const bias = (capacity: number, rng: () => number) => () => rng() * capacity * 10 | 0; + // 遅いZipfを速い疑似関数で代用し偏りを再現する。 + // ILRUのリストをインデクスで置き換える高速化手法はZipfのような + // 最も典型的な偏りのアクセスパターンで50%以下の速度に低速化する場合がある。 + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + const pzipf = (capacity: number, rng: () => number) => () => + Math.floor((rng() * capacity) ** 2 / (5 * capacity / 100 | 0)); it(`Clock simulation ${size.toLocaleString('en')} 10%`, function (done) { const cache = new Clock(size); - const random = bias(Math.ceil(size / 32) * 32, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(Math.ceil(size / 32) * 32, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`Clock simulation ${size.toLocaleString('en')} 10%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`ILRU simulation ${size.toLocaleString('en')} 10%`, function (done) { const cache = new LRUCache({ max: size }); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`ILRU simulation ${size.toLocaleString('en')} 10%`, () => { const key = random(); - cache.get(key) ?? cache.set(key, {}); + cache.get(key) ?? cache.set(key, { s: 'simulation' }); }, done); }); it(`LRU simulation ${size.toLocaleString('en')} 10%`, function (done) { const cache = new LRU(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`LRU simulation ${size.toLocaleString('en')} 10%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`TRC-C simulation ${size.toLocaleString('en')} 10%`, function (done) { const cache = new TRCC(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`TRC-C simulation ${size.toLocaleString('en')} 10%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`TRC-L simulation ${size.toLocaleString('en')} 10%`, function (done) { const cache = new TRCL(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`TRC-L simulation ${size.toLocaleString('en')} 10%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`DWC simulation ${size.toLocaleString('en')} 10%`, function (done) { const cache = new Cache(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`DWC simulation ${size.toLocaleString('en')} 10%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); } - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - const bias = (capacity: number, rng: () => number) => () => rng() * capacity * 2 | 0; + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + const pzipf = (capacity: number, rng: () => number) => () => + Math.floor((rng() * capacity) ** 2 / (35 * capacity / 100 | 0)); it(`Clock simulation ${size.toLocaleString('en')} 50%`, function (done) { const cache = new Clock(size); - const random = bias(Math.ceil(size / 32) * 32, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(Math.ceil(size / 32) * 32, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`Clock simulation ${size.toLocaleString('en')} 50%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`ILRU simulation ${size.toLocaleString('en')} 50%`, function (done) { const cache = new LRUCache({ max: size }); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`ILRU simulation ${size.toLocaleString('en')} 50%`, () => { const key = random(); - cache.get(key) ?? cache.set(key, {}); + cache.get(key) ?? cache.set(key, { s: 'simulation' }); }, done); }); it(`LRU simulation ${size.toLocaleString('en')} 50%`, function (done) { const cache = new LRU(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`LRU simulation ${size.toLocaleString('en')} 50%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`TRC-C simulation ${size.toLocaleString('en')} 50%`, function (done) { const cache = new TRCC(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`TRC-C simulation ${size.toLocaleString('en')} 50%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`TRC-L simulation ${size.toLocaleString('en')} 50%`, function (done) { const cache = new TRCL(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`TRC-L simulation ${size.toLocaleString('en')} 50%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`DWC simulation ${size.toLocaleString('en')} 50%`, function (done) { const cache = new Cache(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`DWC simulation ${size.toLocaleString('en')} 50%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); } - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - const bias = (capacity: number, rng: () => number) => () => rng() * capacity * 1.1 | 0; + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + const pzipf = (capacity: number, rng: () => number) => () => + Math.floor((rng() * capacity) ** 2 / (85 * capacity / 100 | 0)); it(`Clock simulation ${size.toLocaleString('en')} 90%`, function (done) { const cache = new Clock(size); - const random = bias(Math.ceil(size / 32) * 32, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(Math.ceil(size / 32) * 32, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`Clock simulation ${size.toLocaleString('en')} 90%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`ILRU simulation ${size.toLocaleString('en')} 90%`, function (done) { const cache = new LRUCache({ max: size }); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`ILRU simulation ${size.toLocaleString('en')} 90%`, () => { const key = random(); - cache.get(key) ?? cache.set(key, {}); + cache.get(key) ?? cache.set(key, { s: 'simulation' }); }, done); }); it(`LRU simulation ${size.toLocaleString('en')} 90%`, function (done) { const cache = new LRU(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`LRU simulation ${size.toLocaleString('en')} 90%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`TRC-C simulation ${size.toLocaleString('en')} 90%`, function (done) { const cache = new TRCC(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`TRC-C simulation ${size.toLocaleString('en')} 90%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`TRC-L simulation ${size.toLocaleString('en')} 90%`, function (done) { const cache = new TRCL(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`TRC-L simulation ${size.toLocaleString('en')} 90%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); it(`DWC simulation ${size.toLocaleString('en')} 90%`, function (done) { const cache = new Cache(size); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 10; ++i) cache.set(random(), {}); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 10; ++i) cache.set(random(), { s: 'simulation' }); benchmark(`DWC simulation ${size.toLocaleString('en')} 90%`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}); + cache.get(key) ?? cache.add(key, { s: 'simulation' }); }, done); }); } - for (const size of [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { - const bias = (capacity: number, rng: () => number) => () => rng() * capacity * 1.1 | 0; + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + const pzipf = (capacity: number, rng: () => number) => () => + Math.floor((rng() * capacity) ** 2 / (85 * capacity / 100 | 0)); const age = 1000; it(`ILRU simulation ${size.toLocaleString('en')} 90% expire`, captureTimers(function (done) { const cache = new LRUCache({ max: size, ttlAutopurge: true }); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 9; ++i) cache.set(random(), {}); - for (let i = 0; i < size * 1; ++i) cache.set(i, {}, { ttl: age }); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 9; ++i) cache.set(random(), { s: 'simulation' }); + for (let i = 0; i < size * 1; ++i) cache.set(random(), { s: 'simulation' }, { ttl: age }); benchmark(`ILRU simulation ${size.toLocaleString('en')} 90% expire`, () => { const key = random(); - cache.get(key) ?? cache.set(key, {}, { ttl: age }); + cache.get(key) ?? cache.set(key, { s: 'simulation' }, { ttl: age }); }, done); })); it(`DWC simulation ${size.toLocaleString('en')} 90% expire`, function (done) { const cache = new Cache(size, { eagerExpiration: true }); - const random = bias(size, xorshift.random(1)); - for (let i = 0; i < size * 9; ++i) cache.set(random(), {}); - for (let i = 0; i < size * 1; ++i) cache.set(i, {}, { age: age }); + const random = pzipf(size, xorshift.random(1)); + for (let i = 0; i < size * 9; ++i) cache.set(random(), { s: 'simulation' }); + for (let i = 0; i < size * 1; ++i) cache.set(random(), { s: 'simulation' }, { age: age }); benchmark(`DWC simulation ${size.toLocaleString('en')} 90% expire`, () => { const key = random(); - cache.get(key) ?? cache.add(key, {}, { age: age }); + cache.get(key) ?? cache.add(key, { s: 'simulation' }, { age: age }); }, done); }); } + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + it(`Clock set miss ${size.toLocaleString('en')}`, function (done) { + const cache = new Clock(size); + for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + let i = 0; + benchmark(`Clock set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, { s: 'simulation' }), done); + }); + + it(`ILRU set miss ${size.toLocaleString('en')}`, function (done) { + const cache = new LRUCache({ max: size }); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + let i = 0; + benchmark(`ILRU set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, { s: 'simulation' }), done); + }); + + it(`LRU set miss ${size.toLocaleString('en')}`, function (done) { + const cache = new LRU(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + let i = 0; + benchmark(`LRU set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, { s: 'simulation' }), done); + }); + + it(`TRC-C set miss ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCC(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + let i = 0; + benchmark(`TRC-C set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, { s: 'simulation' }), done); + }); + + it(`TRC-L set miss ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCL(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + let i = 0; + benchmark(`TRC-L set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, { s: 'simulation' }), done); + }); + + it(`DWC set miss ${size.toLocaleString('en')}`, function (done) { + const cache = new Cache(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + let i = 0; + benchmark(`DWC set miss ${size.toLocaleString('en')}`, () => cache.set(random() + ++i | 0, { s: 'simulation' }), done); + }); + } + + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + it(`Clock set hit ${size.toLocaleString('en')}`, function (done) { + const cache = new Clock(size); + for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`Clock set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, { s: 'simulation' }), done); + }); + + it(`ILRU set hit ${size.toLocaleString('en')}`, function (done) { + const cache = new LRUCache({ max: size }); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`ILRU set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, { s: 'simulation' }), done); + }); + + it(`LRU set hit ${size.toLocaleString('en')}`, function (done) { + const cache = new LRU(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`LRU set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, { s: 'simulation' }), done); + }); + + it(`TRC-C set hit ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCC(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`TRC-C set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, { s: 'simulation' }), done); + }); + + it(`TRC-L set hit ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCL(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`TRC-L set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, { s: 'simulation' }), done); + }); + + it(`DWC set hit ${size.toLocaleString('en')}`, function (done) { + const cache = new Cache(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`DWC set hit ${size.toLocaleString('en')}`, () => cache.set(random() * size | 0, { s: 'simulation' }), done); + }); + } + + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + it(`Clock get miss ${size.toLocaleString('en')}`, function (done) { + const cache = new Clock(size); + for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`Clock get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`ILRU get miss ${size.toLocaleString('en')}`, function (done) { + const cache = new LRUCache({ max: size }); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`ILRU get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`LRU get miss ${size.toLocaleString('en')}`, function (done) { + const cache = new LRU(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`LRU get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`TRC-C get miss ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCC(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`TRC-C get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`TRC-L get miss ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCL(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`TRC-L get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`DWC get miss ${size.toLocaleString('en')}`, function (done) { + const cache = new Cache(size); + for (let i = 0; i < size; ++i) cache.set(~i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`DWC get miss ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + } + + for (const size of [1e2, 1e3, 1e4, 1e5, 1e6]) { + it(`Clock get hit ${size.toLocaleString('en')}`, function (done) { + const cache = new Clock(size); + for (let i = 0; i < Math.ceil(size / 32) * 32; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`Clock get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`ILRU get hit ${size.toLocaleString('en')}`, function (done) { + const cache = new LRUCache({ max: size }); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`ILRU get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`LRU get hit ${size.toLocaleString('en')}`, function (done) { + const cache = new LRU(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`LRU get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`TRC-C get hit ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCC(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`TRC-C get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`TRC-L get hit ${size.toLocaleString('en')}`, function (done) { + const cache = new TRCL(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`TRC-L get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + + it(`DWC get hit ${size.toLocaleString('en')}`, function (done) { + const cache = new Cache(size); + for (let i = 0; i < size; ++i) cache.set(i, { s: 'simulation' }); + const random = xorshift.random(1); + benchmark(`DWC get hit ${size.toLocaleString('en')}`, () => cache.get(random() * size | 0), done); + }); + } + }); }); diff --git a/src/cache.test.ts b/src/cache.test.ts index 19e9fb87..6c7f3b6f 100644 --- a/src/cache.test.ts +++ b/src/cache.test.ts @@ -406,6 +406,39 @@ describe('Unit: lib/cache', () => { assert(stats.dwc / stats.lru * 100 >>> 0 === 147); }); + it('ratio pzipf 100', function () { + this.timeout(10 * 1e3); + + const capacity = 100; + const lru = new LRU(capacity); + const trc = new TLRU(capacity); + const dwc = new Cache(capacity); + + const trials = capacity * 1000; + const random = xorshift.random(1); + const stats = new Stats(); + const log = { 0: 0 }; + for (let i = 0; i < trials; ++i) { + const key = Math.floor((random() * capacity) ** 2 / (10 * capacity / 100 | 0)); + stats.lru += lru.get(key) ?? +lru.set(key, 1) & 0; + stats.trc += trc.get(key) ?? +trc.set(key, 1) & 0; + stats.dwc += dwc.get(key) ?? +dwc.set(key, 1) & 0; + stats.total += 1; + key in log ? ++log[key] : log[key] = 1; + } + //console.debug(Object.entries(log).sort((a, b) => b[1] - a[1]).map(e => [+e[0], e[1]])); + assert(dwc['LRU'].length + dwc['LFU'].length === dwc['dict'].size); + assert(dwc['dict'].size <= capacity); + console.debug('Cache pzipf 100'); + console.debug('LRU hit ratio', stats.lru * 100 / stats.total); + console.debug('TRC hit ratio', stats.trc * 100 / stats.total); + console.debug('DWC hit ratio', stats.dwc * 100 / stats.total); + console.debug('DWC / LRU hit ratio', `${stats.dwc / stats.lru * 100 | 0}%`); + console.debug('DWC ratio', dwc['partition']! * 100 / capacity | 0, dwc['LFU'].length * 100 / capacity | 0); + console.debug('DWC overlap', dwc['overlapLRU'], dwc['overlapLFU']); + assert(stats.dwc / stats.lru * 100 >>> 0 === 126); + }); + it('ratio zipf 100', function () { this.timeout(10 * 1e3); @@ -803,7 +836,7 @@ describe('Unit: lib/cache', () => { assert(dwc['partition']! * 100 / capacity >>> 0 === 0); }); - it('ratio uneven 1,000', function () { + it('ratio pzipf 1,000', function () { this.timeout(60 * 1e3); const capacity = 1000; @@ -814,25 +847,26 @@ describe('Unit: lib/cache', () => { const trials = capacity * 1000; const random = xorshift.random(1); const stats = new Stats(); + const log = { 0: 0 }; for (let i = 0; i < trials; ++i) { - const key = random() < 0.4 - ? random() * capacity * -1 | 0 - : random() * capacity * 10 | 0; + const key = Math.floor((random() * capacity) ** 2 / (10 * capacity / 100 | 0)); stats.lru += lru.get(key) ?? +lru.set(key, 1) & 0; stats.trc += trc.get(key) ?? +trc.set(key, 1) & 0; stats.dwc += dwc.get(key) ?? +dwc.set(key, 1) & 0; stats.total += 1; + key in log ? ++log[key] : log[key] = 1; } + //console.debug(Object.entries(log).sort((a, b) => b[1] - a[1]).map(e => [+e[0], e[1]])); assert(dwc['LRU'].length + dwc['LFU'].length === dwc['dict'].size); assert(dwc['dict'].size <= capacity); - console.debug('Cache uneven 1,000'); + console.debug('Cache pzipf 1,000'); console.debug('LRU hit ratio', stats.lru * 100 / stats.total); console.debug('TRC hit ratio', stats.trc * 100 / stats.total); console.debug('DWC hit ratio', stats.dwc * 100 / stats.total); console.debug('DWC / LRU hit ratio', `${stats.dwc / stats.lru * 100 | 0}%`); console.debug('DWC ratio', dwc['partition']! * 100 / capacity | 0, dwc['LFU'].length * 100 / capacity | 0); console.debug('DWC overlap', dwc['overlapLRU'], dwc['overlapLFU']); - assert(stats.dwc / stats.lru * 100 >>> 0 === 162); + assert(stats.dwc / stats.lru * 100 >>> 0 === 130); }); }); diff --git a/src/cache.ts b/src/cache.ts index a70de2d2..103f4ff5 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -79,7 +79,9 @@ https://github.com/ben-manes/caffeine/wiki/Efficiency /* # lru-cacheの最適化分析 -最適化前(@6)よりオブジェクト値において50-10%ほど高速化している。 +素朴な実装より非常に大きいヒット率と容量では高速化するがそれ以外の場合は低速化する。 +この最適化により素朴な実装より速くなる範囲は非常に狭く全体的には低速化している。 +巨大な配列を複数要するため空間オーバーヘッドも大きく消費メモリ基準では容量とヒット率が低下する。 ## Map値の数値化 @@ -88,11 +90,8 @@ getは変わらないため読み取り主体の場合効果が低い。 ## インデクスアクセス化 -個別の状態を個別のオブジェクトのプロパティに持たせると最適化されていないプロパティアクセスにより -低速化するためすべての状態を状態別の配列に格納しインデクスアクセスに変換することで高速化している。 -DWCはこの最適化を行っても状態数の多さに比例して増加したオーバーヘッドに相殺され効果を得られない。 -状態をオブジェクトの代わりに配列に入れても最適化されずプロパティ・インデクスとも二段のアクセスは -最適化されないと思われる。 +すべての状態を状態別の配列に格納しインデクスアクセスに変換することで高速化している。 +DWCはこの高速化を行っても状態数の多さに比例して増加したオーバーヘッドに相殺され効果を得られない。 ## TypedArray @@ -706,15 +705,19 @@ class Sweeper>> { private ratioWindow(): number { return ratio( this.window, - [this.currWindowHits, this.prevWindowHits], - [this.currWindowMisses, this.prevWindowMisses], + this.currWindowHits, + this.prevWindowHits, + this.currWindowMisses, + this.prevWindowMisses, 0); } private ratioRoom(): number { return ratio( this.room, - [this.currRoomHits, this.prevRoomHits], - [this.currRoomMisses, this.prevRoomMisses], + this.currRoomHits, + this.prevRoomHits, + this.currRoomMisses, + this.prevRoomMisses, 0); } private processing = false; @@ -779,16 +782,14 @@ class Sweeper>> { function ratio( window: number, - targets: readonly [number, number], - remains: readonly [number, number], + currHits: number, + prevHits: number, + currMisses: number, + prevMisses: number, offset: number, ): number { - assert(targets.length === 2); - assert(targets.length === remains.length); - const currHits = targets[0]; - const prevHits = targets[1]; - const currTotal = currHits + remains[0]; - const prevTotal = prevHits + remains[1]; + const currTotal = currHits + currMisses; + const prevTotal = prevHits + prevMisses; assert(currTotal <= window); const prevRate = prevHits && prevHits * 100 / prevTotal; const currRatio = currTotal * 100 / window - offset; @@ -824,13 +825,13 @@ function ratio2( } return hits * 10000 / total | 0; } -assert(ratio(10, [4, 0], [6, 0], 0) === 4000); -assert(ratio(10, [0, 4], [0, 6], 0) === 4000); -assert(ratio(10, [1, 4], [4, 6], 0) === 3000); -assert(ratio(10, [0, 4], [0, 6], 5) === 4000); -assert(ratio(10, [1, 2], [4, 8], 5) === 2000); -assert(ratio(10, [2, 2], [3, 8], 5) === 2900); -assert(ratio(10, [2, 0], [3, 0], 0) === 4000); +assert(ratio(10, 4, 0, 6, 0, 0) === 4000); +assert(ratio(10, 0, 4, 0, 6, 0) === 4000); +assert(ratio(10, 1, 4, 4, 6, 0) === 3000); +assert(ratio(10, 0, 4, 0, 6, 5) === 4000); +assert(ratio(10, 1, 2, 4, 8, 5) === 2000); +assert(ratio(10, 2, 2, 3, 8, 5) === 2900); +assert(ratio(10, 2, 0, 3, 0, 0) === 4000); assert(ratio2(10, [4, 0], [6, 0], 0) === 4000); assert(ratio2(10, [0, 4], [0, 6], 0) === 4000); assert(ratio2(10, [1, 4], [4, 6], 0) === 3000);