Comparing version 0.0.695 to 0.0.696
87
cache.ts
@@ -128,2 +128,4 @@ import { max, min, ceil, round } from './alias'; | ||
readonly window?: number; | ||
// Max costs. | ||
// Range: L- | ||
readonly resource?: number; | ||
@@ -144,4 +146,2 @@ readonly age?: number; | ||
readonly sample?: number; | ||
// Max costs. | ||
// Range: L- | ||
readonly resolution?: number; | ||
@@ -202,2 +202,3 @@ readonly offset?: number; | ||
this.test = settings.test!; | ||
assert(settings.resource === opts.resource); | ||
} | ||
@@ -521,3 +522,3 @@ private readonly settings: Cache.Options<K, V> = { | ||
} | ||
public resize(capacity: number, resource: number = capacity): void { | ||
public resize(capacity: number, resource?: number): void { | ||
if (capacity >>> 0 !== capacity) throw new Error(`Spica: Cache: Capacity must be integer.`); | ||
@@ -531,3 +532,3 @@ if (capacity >= 1 === false) throw new Error(`Spica: Cache: Capacity must be 1 or more.`); | ||
this.limit = capacity - (capacity * this.settings.scope! / 100 >>> 0); | ||
this.resource = resource; | ||
this.resource = resource ?? this.settings.resource ?? capacity; | ||
this.stats.resize(window); | ||
@@ -591,16 +592,16 @@ this.sweeper.resize(capacity, this.settings.sweep!.window!, this.settings.sweep!.range!); | ||
if (stats.subtotal() * RESOLUTION % capacity !== 0 || !stats.isReady()) return; | ||
const lenR = LRU.length; | ||
const lenF = LFU.length; | ||
const lenOR = this.overlapLRU; | ||
const lenOF = this.overlapLFU; | ||
const leverage = min((lenF - lenOR + lenOF) * 1000 / (lenR + lenF) | 0, 1000); | ||
const rateR = stats.rateLRU(); | ||
const rateF = 10000 - rateR; | ||
const densityR = this.densityR = rateR * leverage; | ||
const densityF = this.densityF = rateF * (1000 - leverage); | ||
const densityFO = stats.offset && stats.rateLFU(true) * (1000 - leverage); | ||
const partR = LRU.length; | ||
const partF = LFU.length; | ||
const overlapR = this.overlapLRU; | ||
const overlapF = this.overlapLFU; | ||
const leverage = min((partF - overlapR + overlapF) * 1000 / (partR + partF) | 0, 1000); | ||
const ratioR = stats.ratioLRU(); | ||
const ratioF = 10000 - ratioR; | ||
const densityR = this.densityR = ratioR * leverage; | ||
const densityF = this.densityF = ratioF * (1000 - leverage); | ||
const densityFO = stats.offset && stats.ratioLFU(true) * (1000 - leverage); | ||
// 操作頻度を超えてキャッシュ比率を増減させても余剰比率の消化が追いつかず無駄 | ||
// LRUの下限設定ではLRU拡大の要否を迅速に判定できないためLFUのヒット率低下の検出で代替する | ||
if (this.partition > 0 && (densityR > densityF || stats.offset !== 0 && densityF * 100 < densityFO * (100 - stats.offset))) { | ||
if (lenR >= this.capacity - this.partition) { | ||
if (partR >= this.capacity - this.partition) { | ||
this.partition = max(this.partition - this.unit, 0); | ||
@@ -611,3 +612,3 @@ } | ||
if (this.partition < this.limit && densityF > densityR) { | ||
if (lenF >= this.partition) { | ||
if (partF >= this.partition) { | ||
this.partition = min(this.partition + this.unit, this.limit); | ||
@@ -621,3 +622,3 @@ } | ||
class Stats { | ||
public static rate( | ||
public static ratio( | ||
window: number, | ||
@@ -661,4 +662,4 @@ targets: readonly [number, number], | ||
} | ||
public rateLRU(offset = false): number { | ||
return Stats.rate( | ||
public ratioLRU(offset = false): number { | ||
return Stats.ratio( | ||
this.window, | ||
@@ -669,4 +670,4 @@ [this.currLRUHits, this.prevLRUHits], | ||
} | ||
public rateLFU(offset = false): number { | ||
return Stats.rate( | ||
public ratioLFU(offset = false): number { | ||
return Stats.ratio( | ||
this.window, | ||
@@ -699,3 +700,3 @@ [this.currLFUHits, this.prevLFUHits], | ||
class StatsExperimental extends Stats { | ||
public static override rate( | ||
public static override ratio( | ||
window: number, | ||
@@ -718,5 +719,5 @@ targets: readonly number[], | ||
if (subratio <= 0) continue; | ||
const rate = window * subratio / subtotal; | ||
total += subtotal * rate; | ||
hits += targets[i] * rate; | ||
const r = window * subratio / subtotal; | ||
total += subtotal * r; | ||
hits += targets[i] * r; | ||
ratio -= subratio; | ||
@@ -755,7 +756,7 @@ if (ratio <= 0) break; | ||
} | ||
public override rateLRU(offset = false): number { | ||
return StatsExperimental.rate(this.window, this.LRU, this.LFU, +offset && this.offset); | ||
public override ratioLRU(offset = false): number { | ||
return StatsExperimental.ratio(this.window, this.LRU, this.LFU, +offset && this.offset); | ||
} | ||
public override rateLFU(offset = false): number { | ||
return StatsExperimental.rate(this.window, this.LFU, this.LRU, +offset && this.offset); | ||
public override ratioLFU(offset = false): number { | ||
return StatsExperimental.ratio(this.window, this.LFU, this.LRU, +offset && this.offset); | ||
} | ||
@@ -799,14 +800,14 @@ public override subtotal(): number { | ||
assert(Stats.rate(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(Stats.rate(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(Stats.rate(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(Stats.rate(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(Stats.rate(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(Stats.rate(10, [2, 2], [3, 8], 5) === 2900); | ||
assert(StatsExperimental.rate(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(StatsExperimental.rate(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(StatsExperimental.rate(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(StatsExperimental.rate(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(StatsExperimental.rate(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(StatsExperimental.rate(10, [2, 2], [3, 8], 5) === 2900); | ||
assert(Stats.ratio(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(Stats.ratio(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(Stats.ratio(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(Stats.ratio(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(Stats.ratio(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(Stats.ratio(10, [2, 2], [3, 8], 5) === 2900); | ||
assert(StatsExperimental.ratio(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(StatsExperimental.ratio(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(StatsExperimental.ratio(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(StatsExperimental.ratio(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(StatsExperimental.ratio(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(StatsExperimental.ratio(10, [2, 2], [3, 8], 5) === 2900); | ||
@@ -849,3 +850,3 @@ // Transitive Wide MRU with Cyclic Replacement | ||
if (this.prevHits === 0 && this.prevMisses === 0) return false; | ||
const rate = Stats.rate( | ||
const ratio = Stats.ratio( | ||
this.window, | ||
@@ -855,3 +856,3 @@ [this.currHits, this.prevHits], | ||
0); | ||
return this.active ??= rate < this.threshold; | ||
return this.active ??= ratio < this.threshold; | ||
} | ||
@@ -858,0 +859,0 @@ private processing = false; |
@@ -116,16 +116,29 @@ import { Heap } from './heap'; | ||
const node = heap.insert(1, 1); | ||
heap.insert(2, 2); | ||
heap.insert(3, 3); | ||
const nodes = [ | ||
heap.insert(1, 1), | ||
heap.insert(2, 2), | ||
heap.insert(3, 3), | ||
heap.insert(4, 4), | ||
]; | ||
assert.deepStrictEqual(inspect(heap), [ | ||
[3, 3, 1], | ||
[1, 1, 2], | ||
[4, 4, 1], | ||
[3, 3, 2], | ||
[2, 2, 3], | ||
[1, 1, 4], | ||
]); | ||
assert.deepStrictEqual(heap.delete(node), 1); | ||
assert.deepStrictEqual(heap.delete(nodes[0]), 1); | ||
assert.deepStrictEqual(inspect(heap), [ | ||
[3, 3, 1], | ||
[4, 4, 1], | ||
[3, 3, 2], | ||
[2, 2, 3], | ||
undefined, | ||
]); | ||
assert.deepStrictEqual(heap.delete(nodes[2]), 3); | ||
assert.deepStrictEqual(inspect(heap), [ | ||
[4, 4, 1], | ||
[2, 2, 2], | ||
undefined, | ||
undefined, | ||
]); | ||
@@ -132,0 +145,0 @@ }); |
@@ -76,4 +76,4 @@ import { List } from './list'; | ||
swap(array, index, this.$length--); | ||
sort(this.cmp, array, index, this.$length, this.stable); | ||
array[this.$length] = undefined as any; | ||
sort(this.cmp, array, index, this.$length, this.stable); | ||
return node[1]; | ||
@@ -80,0 +80,0 @@ } |
{ | ||
"name": "spica", | ||
"version": "0.0.695", | ||
"version": "0.0.696", | ||
"description": "Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.", | ||
@@ -5,0 +5,0 @@ "private": false, |
@@ -128,2 +128,4 @@ import { max, min, ceil, round } from './alias'; | ||
readonly window?: number; | ||
// Max costs. | ||
// Range: L- | ||
readonly resource?: number; | ||
@@ -144,4 +146,2 @@ readonly age?: number; | ||
readonly sample?: number; | ||
// Max costs. | ||
// Range: L- | ||
readonly resolution?: number; | ||
@@ -202,2 +202,3 @@ readonly offset?: number; | ||
this.test = settings.test!; | ||
assert(settings.resource === opts.resource); | ||
} | ||
@@ -521,3 +522,3 @@ private readonly settings: Cache.Options<K, V> = { | ||
} | ||
public resize(capacity: number, resource: number = capacity): void { | ||
public resize(capacity: number, resource?: number): void { | ||
if (capacity >>> 0 !== capacity) throw new Error(`Spica: Cache: Capacity must be integer.`); | ||
@@ -531,3 +532,3 @@ if (capacity >= 1 === false) throw new Error(`Spica: Cache: Capacity must be 1 or more.`); | ||
this.limit = capacity - (capacity * this.settings.scope! / 100 >>> 0); | ||
this.resource = resource; | ||
this.resource = resource ?? this.settings.resource ?? capacity; | ||
this.stats.resize(window); | ||
@@ -591,16 +592,16 @@ this.sweeper.resize(capacity, this.settings.sweep!.window!, this.settings.sweep!.range!); | ||
if (stats.subtotal() * RESOLUTION % capacity !== 0 || !stats.isReady()) return; | ||
const lenR = LRU.length; | ||
const lenF = LFU.length; | ||
const lenOR = this.overlapLRU; | ||
const lenOF = this.overlapLFU; | ||
const leverage = min((lenF - lenOR + lenOF) * 1000 / (lenR + lenF) | 0, 1000); | ||
const rateR = stats.rateLRU(); | ||
const rateF = 10000 - rateR; | ||
const densityR = this.densityR = rateR * leverage; | ||
const densityF = this.densityF = rateF * (1000 - leverage); | ||
const densityFO = stats.offset && stats.rateLFU(true) * (1000 - leverage); | ||
const partR = LRU.length; | ||
const partF = LFU.length; | ||
const overlapR = this.overlapLRU; | ||
const overlapF = this.overlapLFU; | ||
const leverage = min((partF - overlapR + overlapF) * 1000 / (partR + partF) | 0, 1000); | ||
const ratioR = stats.ratioLRU(); | ||
const ratioF = 10000 - ratioR; | ||
const densityR = this.densityR = ratioR * leverage; | ||
const densityF = this.densityF = ratioF * (1000 - leverage); | ||
const densityFO = stats.offset && stats.ratioLFU(true) * (1000 - leverage); | ||
// 操作頻度を超えてキャッシュ比率を増減させても余剰比率の消化が追いつかず無駄 | ||
// LRUの下限設定ではLRU拡大の要否を迅速に判定できないためLFUのヒット率低下の検出で代替する | ||
if (this.partition > 0 && (densityR > densityF || stats.offset !== 0 && densityF * 100 < densityFO * (100 - stats.offset))) { | ||
if (lenR >= this.capacity - this.partition) { | ||
if (partR >= this.capacity - this.partition) { | ||
this.partition = max(this.partition - this.unit, 0); | ||
@@ -611,3 +612,3 @@ } | ||
if (this.partition < this.limit && densityF > densityR) { | ||
if (lenF >= this.partition) { | ||
if (partF >= this.partition) { | ||
this.partition = min(this.partition + this.unit, this.limit); | ||
@@ -621,3 +622,3 @@ } | ||
class Stats { | ||
public static rate( | ||
public static ratio( | ||
window: number, | ||
@@ -661,4 +662,4 @@ targets: readonly [number, number], | ||
} | ||
public rateLRU(offset = false): number { | ||
return Stats.rate( | ||
public ratioLRU(offset = false): number { | ||
return Stats.ratio( | ||
this.window, | ||
@@ -669,4 +670,4 @@ [this.currLRUHits, this.prevLRUHits], | ||
} | ||
public rateLFU(offset = false): number { | ||
return Stats.rate( | ||
public ratioLFU(offset = false): number { | ||
return Stats.ratio( | ||
this.window, | ||
@@ -699,3 +700,3 @@ [this.currLFUHits, this.prevLFUHits], | ||
class StatsExperimental extends Stats { | ||
public static override rate( | ||
public static override ratio( | ||
window: number, | ||
@@ -718,5 +719,5 @@ targets: readonly number[], | ||
if (subratio <= 0) continue; | ||
const rate = window * subratio / subtotal; | ||
total += subtotal * rate; | ||
hits += targets[i] * rate; | ||
const r = window * subratio / subtotal; | ||
total += subtotal * r; | ||
hits += targets[i] * r; | ||
ratio -= subratio; | ||
@@ -755,7 +756,7 @@ if (ratio <= 0) break; | ||
} | ||
public override rateLRU(offset = false): number { | ||
return StatsExperimental.rate(this.window, this.LRU, this.LFU, +offset && this.offset); | ||
public override ratioLRU(offset = false): number { | ||
return StatsExperimental.ratio(this.window, this.LRU, this.LFU, +offset && this.offset); | ||
} | ||
public override rateLFU(offset = false): number { | ||
return StatsExperimental.rate(this.window, this.LFU, this.LRU, +offset && this.offset); | ||
public override ratioLFU(offset = false): number { | ||
return StatsExperimental.ratio(this.window, this.LFU, this.LRU, +offset && this.offset); | ||
} | ||
@@ -799,14 +800,14 @@ public override subtotal(): number { | ||
assert(Stats.rate(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(Stats.rate(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(Stats.rate(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(Stats.rate(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(Stats.rate(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(Stats.rate(10, [2, 2], [3, 8], 5) === 2900); | ||
assert(StatsExperimental.rate(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(StatsExperimental.rate(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(StatsExperimental.rate(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(StatsExperimental.rate(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(StatsExperimental.rate(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(StatsExperimental.rate(10, [2, 2], [3, 8], 5) === 2900); | ||
assert(Stats.ratio(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(Stats.ratio(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(Stats.ratio(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(Stats.ratio(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(Stats.ratio(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(Stats.ratio(10, [2, 2], [3, 8], 5) === 2900); | ||
assert(StatsExperimental.ratio(10, [4, 0], [6, 0], 0) === 4000); | ||
assert(StatsExperimental.ratio(10, [0, 4], [0, 6], 0) === 4000); | ||
assert(StatsExperimental.ratio(10, [1, 4], [4, 6], 0) === 3000); | ||
assert(StatsExperimental.ratio(10, [0, 4], [0, 6], 5) === 4000); | ||
assert(StatsExperimental.ratio(10, [1, 2], [4, 8], 5) === 2000); | ||
assert(StatsExperimental.ratio(10, [2, 2], [3, 8], 5) === 2900); | ||
@@ -849,3 +850,3 @@ // Transitive Wide MRU with Cyclic Replacement | ||
if (this.prevHits === 0 && this.prevMisses === 0) return false; | ||
const rate = Stats.rate( | ||
const ratio = Stats.ratio( | ||
this.window, | ||
@@ -855,3 +856,3 @@ [this.currHits, this.prevHits], | ||
0); | ||
return this.active ??= rate < this.threshold; | ||
return this.active ??= ratio < this.threshold; | ||
} | ||
@@ -858,0 +859,0 @@ private processing = false; |
@@ -116,16 +116,29 @@ import { Heap } from './heap'; | ||
const node = heap.insert(1, 1); | ||
heap.insert(2, 2); | ||
heap.insert(3, 3); | ||
const nodes = [ | ||
heap.insert(1, 1), | ||
heap.insert(2, 2), | ||
heap.insert(3, 3), | ||
heap.insert(4, 4), | ||
]; | ||
assert.deepStrictEqual(inspect(heap), [ | ||
[3, 3, 1], | ||
[1, 1, 2], | ||
[4, 4, 1], | ||
[3, 3, 2], | ||
[2, 2, 3], | ||
[1, 1, 4], | ||
]); | ||
assert.deepStrictEqual(heap.delete(node), 1); | ||
assert.deepStrictEqual(heap.delete(nodes[0]), 1); | ||
assert.deepStrictEqual(inspect(heap), [ | ||
[3, 3, 1], | ||
[4, 4, 1], | ||
[3, 3, 2], | ||
[2, 2, 3], | ||
undefined, | ||
]); | ||
assert.deepStrictEqual(heap.delete(nodes[2]), 3); | ||
assert.deepStrictEqual(inspect(heap), [ | ||
[4, 4, 1], | ||
[2, 2, 2], | ||
undefined, | ||
undefined, | ||
]); | ||
@@ -132,0 +145,0 @@ }); |
@@ -76,4 +76,4 @@ import { List } from './list'; | ||
swap(array, index, this.$length--); | ||
sort(this.cmp, array, index, this.$length, this.stable); | ||
array[this.$length] = undefined as any; | ||
sort(this.cmp, array, index, this.$length, this.stable); | ||
return node[1]; | ||
@@ -80,0 +80,0 @@ } |
1135411
31839