Below you can watch four eviction policies race through the same stream of page requests. The cache holds 4 slots. Each column shows what the policy keeps in cache at every step โ green means a hit, red means a miss.
<div id="controls">
<label>Preset: </label>
<button class="preset" data-seq="1,2,3,4,1,2,5,1,2,3,4,5">Recency-heavy</button>
<button class="preset" data-seq="1,2,1,3,1,2,1,4,1,2,1,3">Frequency-heavy</button>
<button class="preset" data-seq="1,2,3,4,5,1,2,6,1,3,2,1">Mixed</button>
</div>
<div id="custom-row">
<label for="seq">Custom sequence (comma-separated page IDs 1โ8):</label>
<input id="seq" type="text" value="1,2,3,4,1,2,5,1,2,3,4,5" />
<button id="run">Run</button>
</div>
<div id="legend">
<span class="hit-badge">Hit</span>
<span class="miss-badge">Miss</span>
</div>
<div id="table-wrap">
<table id="results">
<thead>
<tr>
<th>Step</th>
<th>Page</th>
<th>LRU</th>
<th>LFU</th>
<th>CLOCK</th>
<th>ARC</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="summary"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
#controls { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
#controls label { font-weight: 600; }
.preset { padding: .3rem .65rem; font: 600 13px system-ui; background: #e8eef3; border: 1px solid #b0bec8;
border-radius: 6px; cursor: pointer; }
.preset:hover { background: #d0dce6; }
#custom-row { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
#custom-row label { font-weight: 600; }
#seq { flex: 1; min-width: 180px; padding: .3rem .5rem; border: 1px solid #b0bec8; border-radius: 6px; font-size: 13px; }
#run { padding: .3rem .75rem; font: 600 13px system-ui; background: #1d3557; color: #fff;
border: none; border-radius: 6px; cursor: pointer; }
#run:hover { background: #2e4f7a; }
#legend { display: flex; gap: .5rem; margin-bottom: .4rem; }
.hit-badge, .miss-badge { padding: .15rem .5rem; border-radius: 4px; font: 600 12px system-ui; }
.hit-badge { background: #d4edda; color: #155724; }
.miss-badge { background: #f8d7da; color: #721c24; }
#table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; }
th { background: #e8eef3; padding: .35rem .5rem; text-align: center; font-weight: 700;
border: 1px solid #cdd9e3; white-space: nowrap; }
td { padding: .3rem .45rem; border: 1px solid #cdd9e3; text-align: center; white-space: nowrap; }
tr:nth-child(even) td { background: #f5f8fa; }
.cell-hit { background: #d4edda !important; color: #155724; font-weight: 700; }
.cell-miss { background: #f8d7da !important; color: #721c24; font-weight: 700; }
.cache-state { font-size: 11px; color: #555; display: block; }
#summary { margin-top: .6rem; display: flex; gap: 1rem; flex-wrap: wrap; }
.policy-score { padding: .35rem .7rem; border-radius: 6px; border: 1px solid #cdd9e3;
font: 600 13px system-ui; background: #f5f8fa; }
.best { background: #d4edda; border-color: #28a745; color: #155724; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Cache eviction policies: LRU, LFU, CLOCK, ARC
// Each policy is implemented from scratch, returns {hit, cacheState} per access.
const CAPACITY = 4;
// โโ LRU โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeLRU(cap) {
let order = []; // most-recent last
return {
access(page) {
const idx = order.indexOf(page);
const hit = idx !== -1;
if (hit) order.splice(idx, 1);
else if (order.length >= cap) order.shift();
order.push(page);
return { hit, state: [...order].reverse() };
}
};
}
// โโ LFU โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeLFU(cap) {
let freq = {}, cache = new Set(), minFreq = 0;
let freqBuckets = {}; // freq -> ordered Set (insertion-ordered)
function addToFreq(f, page) {
if (!freqBuckets[f]) freqBuckets[f] = new Set();
freqBuckets[f].add(page);
}
function removeFromFreq(f, page) {
if (freqBuckets[f]) { freqBuckets[f].delete(page); if (!freqBuckets[f].size) delete freqBuckets[f]; }
}
return {
access(page) {
if (cache.has(page)) {
const f = freq[page];
removeFromFreq(f, page);
freq[page] = f + 1;
addToFreq(f + 1, page);
if (!freqBuckets[minFreq]) minFreq = f + 1;
return { hit: true, state: [...cache] };
}
if (cache.size >= cap) {
// evict least-frequent (first inserted at minFreq)
const victim = freqBuckets[minFreq].values().next().value;
removeFromFreq(minFreq, victim);
cache.delete(victim);
delete freq[victim];
}
cache.add(page);
freq[page] = 1;
addToFreq(1, page);
minFreq = 1;
return { hit: false, state: [...cache] };
}
};
}
// โโ CLOCK โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeCLOCK(cap) {
let slots = new Array(cap).fill(null); // {page, ref}
let hand = 0;
let pageToSlot = {};
return {
access(page) {
if (page in pageToSlot) {
slots[pageToSlot[page]].ref = 1;
return { hit: true, state: slots.filter(s => s).map(s => s.page) };
}
// find slot to evict
while (true) {
const s = slots[hand];
if (!s || !s.ref) {
if (s) delete pageToSlot[s.page];
pageToSlot[page] = hand;
slots[hand] = { page, ref: 1 };
hand = (hand + 1) % cap;
break;
}
slots[hand].ref = 0;
hand = (hand + 1) % cap;
}
return { hit: false, state: slots.filter(s => s).map(s => s.page) };
}
};
}
// โโ ARC โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeARC(cap) {
// T1: recent once, T2: recent โฅ2, B1: ghost of T1, B2: ghost of T2
let T1 = [], T2 = [], B1 = [], B2 = [];
let p = 0; // target size of T1
function inList(lst, x) { return lst.indexOf(x) !== -1; }
function remove(lst, x) { const i = lst.indexOf(x); if (i !== -1) lst.splice(i, 1); }
function replace(hit_in_b2) {
const t1big = T1.length > p || (hit_in_b2 && T1.length === p);
if (T1.length > 0 && t1big) {
const victim = T1.shift();
B1.push(victim);
} else if (T2.length > 0) {
const victim = T2.shift();
B2.push(victim);
} else if (T1.length > 0) {
const victim = T1.shift();
B1.push(victim);
}
}
return {
access(page) {
let hit = false;
if (inList(T1, page)) {
hit = true;
remove(T1, page); T2.push(page);
} else if (inList(T2, page)) {
hit = true;
remove(T2, page); T2.push(page);
} else {
if (inList(B1, page)) {
p = Math.min(cap, p + Math.max(1, Math.floor(B2.length / B1.length)));
replace(false);
remove(B1, page); T2.push(page);
} else if (inList(B2, page)) {
p = Math.max(0, p - Math.max(1, Math.floor(B1.length / B2.length)));
replace(true);
remove(B2, page); T2.push(page);
} else {
const total = T1.length + T2.length;
if (total >= cap) replace(false);
T1.push(page);
}
}
const state = [...T1, ...T2];
return { hit, state };
}
};
}
// โโ Runner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function runAll(seq) {
const lru = makeLRU(CAPACITY);
const lfu = makeLFU(CAPACITY);
const clock = makeCLOCK(CAPACITY);
const arc = makeARC(CAPACITY);
return seq.map(page => ({
page,
lru: lru.access(page),
lfu: lfu.access(page),
clock: clock.access(page),
arc: arc.access(page)
}));
}
function parseSeq(raw) {
return raw.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n) && n >= 1 && n <= 8);
}
function render(seq) {
const results = runAll(seq);
const tbody = document.querySelector('#results tbody');
tbody.innerHTML = '';
const hits = { lru: 0, lfu: 0, clock: 0, arc: 0 };
results.forEach((r, i) => {
const tr = document.createElement('tr');
const policyKeys = ['lru', 'lfu', 'clock', 'arc'];
let cells = `<td>${i + 1}</td><td><b>P${r.page}</b></td>`;
policyKeys.forEach(k => {
const { hit, state } = r[k];
if (hit) hits[k]++;
const cls = hit ? 'cell-hit' : 'cell-miss';
const label = hit ? 'HIT' : 'MISS';
const cacheStr = state.map(p => `P${p}`).join(' ');
cells += `<td class="${cls}">${label}<span class="cache-state">[${cacheStr}]</span></td>`;
});
tr.innerHTML = cells;
tbody.appendChild(tr);
});
const n = seq.length;
const summary = document.getElementById('summary');
const keys = ['lru', 'lfu', 'clock', 'arc'];
const labels = ['LRU', 'LFU', 'CLOCK', 'ARC'];
const maxHit = Math.max(...keys.map(k => hits[k]));
summary.innerHTML = keys.map((k, i) => {
const pct = n ? ((hits[k] / n) * 100).toFixed(0) : 0;
const best = hits[k] === maxHit ? ' best' : '';
return fmt('<span class="policy-score%best%">%v%: %v2%/%n% hits (%pct%%)</span>', { best: best, v: labels[i], v2: hits[k], n: n, pct: pct });
}).join('');
console.log('Cache eviction results:', hits, '| seq:', seq.join(','));
}
document.querySelectorAll('.preset').forEach(btn => {
btn.addEventListener('click', () => {
const seqStr = btn.dataset.seq;
document.getElementById('seq').value = seqStr;
render(parseSeq(seqStr));
});
});
document.getElementById('run').addEventListener('click', () => {
render(parseSeq(document.getElementById('seq').value));
});
// Run default
render(parseSeq(document.getElementById('seq').value));
<div id="controls">
<label>Preset: </label>
<button class="preset" data-seq="1,2,3,4,1,2,5,1,2,3,4,5">Recency-heavy</button>
<button class="preset" data-seq="1,2,1,3,1,2,1,4,1,2,1,3">Frequency-heavy</button>
<button class="preset" data-seq="1,2,3,4,5,1,2,6,1,3,2,1">Mixed</button>
</div>
<div id="custom-row">
<label for="seq">Custom sequence (comma-separated page IDs 1โ8):</label>
<input id="seq" type="text" value="1,2,3,4,1,2,5,1,2,3,4,5" />
<button id="run">Run</button>
</div>
<div id="legend">
<span class="hit-badge">Hit</span>
<span class="miss-badge">Miss</span>
</div>
<div id="table-wrap">
<table id="results">
<thead>
<tr>
<th>Step</th>
<th>Page</th>
<th>LRU</th>
<th>LFU</th>
<th>CLOCK</th>
<th>ARC</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="summary"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
#controls { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
#controls label { font-weight: 600; }
.preset { padding: .3rem .65rem; font: 600 13px system-ui; background: #e8eef3; border: 1px solid #b0bec8;
border-radius: 6px; cursor: pointer; }
.preset:hover { background: #d0dce6; }
#custom-row { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
#custom-row label { font-weight: 600; }
#seq { flex: 1; min-width: 180px; padding: .3rem .5rem; border: 1px solid #b0bec8; border-radius: 6px; font-size: 13px; }
#run { padding: .3rem .75rem; font: 600 13px system-ui; background: #1d3557; color: #fff;
border: none; border-radius: 6px; cursor: pointer; }
#run:hover { background: #2e4f7a; }
#legend { display: flex; gap: .5rem; margin-bottom: .4rem; }
.hit-badge, .miss-badge { padding: .15rem .5rem; border-radius: 4px; font: 600 12px system-ui; }
.hit-badge { background: #d4edda; color: #155724; }
.miss-badge { background: #f8d7da; color: #721c24; }
#table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; }
th { background: #e8eef3; padding: .35rem .5rem; text-align: center; font-weight: 700;
border: 1px solid #cdd9e3; white-space: nowrap; }
td { padding: .3rem .45rem; border: 1px solid #cdd9e3; text-align: center; white-space: nowrap; }
tr:nth-child(even) td { background: #f5f8fa; }
.cell-hit { background: #d4edda !important; color: #155724; font-weight: 700; }
.cell-miss { background: #f8d7da !important; color: #721c24; font-weight: 700; }
.cache-state { font-size: 11px; color: #555; display: block; }
#summary { margin-top: .6rem; display: flex; gap: 1rem; flex-wrap: wrap; }
.policy-score { padding: .35rem .7rem; border-radius: 6px; border: 1px solid #cdd9e3;
font: 600 13px system-ui; background: #f5f8fa; }
.best { background: #d4edda; border-color: #28a745; color: #155724; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Cache eviction policies: LRU, LFU, CLOCK, ARC
// Each policy is implemented from scratch, returns {hit, cacheState} per access.
const CAPACITY = 4;
// โโ LRU โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeLRU(cap) {
let order = []; // most-recent last
return {
access(page) {
const idx = order.indexOf(page);
const hit = idx !== -1;
if (hit) order.splice(idx, 1);
else if (order.length >= cap) order.shift();
order.push(page);
return { hit, state: [...order].reverse() };
}
};
}
// โโ LFU โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeLFU(cap) {
let freq = {}, cache = new Set(), minFreq = 0;
let freqBuckets = {}; // freq -> ordered Set (insertion-ordered)
function addToFreq(f, page) {
if (!freqBuckets[f]) freqBuckets[f] = new Set();
freqBuckets[f].add(page);
}
function removeFromFreq(f, page) {
if (freqBuckets[f]) { freqBuckets[f].delete(page); if (!freqBuckets[f].size) delete freqBuckets[f]; }
}
return {
access(page) {
if (cache.has(page)) {
const f = freq[page];
removeFromFreq(f, page);
freq[page] = f + 1;
addToFreq(f + 1, page);
if (!freqBuckets[minFreq]) minFreq = f + 1;
return { hit: true, state: [...cache] };
}
if (cache.size >= cap) {
// evict least-frequent (first inserted at minFreq)
const victim = freqBuckets[minFreq].values().next().value;
removeFromFreq(minFreq, victim);
cache.delete(victim);
delete freq[victim];
}
cache.add(page);
freq[page] = 1;
addToFreq(1, page);
minFreq = 1;
return { hit: false, state: [...cache] };
}
};
}
// โโ CLOCK โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeCLOCK(cap) {
let slots = new Array(cap).fill(null); // {page, ref}
let hand = 0;
let pageToSlot = {};
return {
access(page) {
if (page in pageToSlot) {
slots[pageToSlot[page]].ref = 1;
return { hit: true, state: slots.filter(s => s).map(s => s.page) };
}
// find slot to evict
while (true) {
const s = slots[hand];
if (!s || !s.ref) {
if (s) delete pageToSlot[s.page];
pageToSlot[page] = hand;
slots[hand] = { page, ref: 1 };
hand = (hand + 1) % cap;
break;
}
slots[hand].ref = 0;
hand = (hand + 1) % cap;
}
return { hit: false, state: slots.filter(s => s).map(s => s.page) };
}
};
}
// โโ ARC โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function makeARC(cap) {
// T1: recent once, T2: recent โฅ2, B1: ghost of T1, B2: ghost of T2
let T1 = [], T2 = [], B1 = [], B2 = [];
let p = 0; // target size of T1
function inList(lst, x) { return lst.indexOf(x) !== -1; }
function remove(lst, x) { const i = lst.indexOf(x); if (i !== -1) lst.splice(i, 1); }
function replace(hit_in_b2) {
const t1big = T1.length > p || (hit_in_b2 && T1.length === p);
if (T1.length > 0 && t1big) {
const victim = T1.shift();
B1.push(victim);
} else if (T2.length > 0) {
const victim = T2.shift();
B2.push(victim);
} else if (T1.length > 0) {
const victim = T1.shift();
B1.push(victim);
}
}
return {
access(page) {
let hit = false;
if (inList(T1, page)) {
hit = true;
remove(T1, page); T2.push(page);
} else if (inList(T2, page)) {
hit = true;
remove(T2, page); T2.push(page);
} else {
if (inList(B1, page)) {
p = Math.min(cap, p + Math.max(1, Math.floor(B2.length / B1.length)));
replace(false);
remove(B1, page); T2.push(page);
} else if (inList(B2, page)) {
p = Math.max(0, p - Math.max(1, Math.floor(B1.length / B2.length)));
replace(true);
remove(B2, page); T2.push(page);
} else {
const total = T1.length + T2.length;
if (total >= cap) replace(false);
T1.push(page);
}
}
const state = [...T1, ...T2];
return { hit, state };
}
};
}
// โโ Runner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function runAll(seq) {
const lru = makeLRU(CAPACITY);
const lfu = makeLFU(CAPACITY);
const clock = makeCLOCK(CAPACITY);
const arc = makeARC(CAPACITY);
return seq.map(page => ({
page,
lru: lru.access(page),
lfu: lfu.access(page),
clock: clock.access(page),
arc: arc.access(page)
}));
}
function parseSeq(raw) {
return raw.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n) && n >= 1 && n <= 8);
}
function render(seq) {
const results = runAll(seq);
const tbody = document.querySelector('#results tbody');
tbody.innerHTML = '';
const hits = { lru: 0, lfu: 0, clock: 0, arc: 0 };
results.forEach((r, i) => {
const tr = document.createElement('tr');
const policyKeys = ['lru', 'lfu', 'clock', 'arc'];
let cells = `<td>${i + 1}</td><td><b>P${r.page}</b></td>`;
policyKeys.forEach(k => {
const { hit, state } = r[k];
if (hit) hits[k]++;
const cls = hit ? 'cell-hit' : 'cell-miss';
const label = hit ? 'HIT' : 'MISS';
const cacheStr = state.map(p => `P${p}`).join(' ');
cells += `<td class="${cls}">${label}<span class="cache-state">[${cacheStr}]</span></td>`;
});
tr.innerHTML = cells;
tbody.appendChild(tr);
});
const n = seq.length;
const summary = document.getElementById('summary');
const keys = ['lru', 'lfu', 'clock', 'arc'];
const labels = ['LRU', 'LFU', 'CLOCK', 'ARC'];
const maxHit = Math.max(...keys.map(k => hits[k]));
summary.innerHTML = keys.map((k, i) => {
const pct = n ? ((hits[k] / n) * 100).toFixed(0) : 0;
const best = hits[k] === maxHit ? ' best' : '';
return fmt('<span class="policy-score%best%">%v%: %v2%/%n% hits (%pct%%)</span>', { best: best, v: labels[i], v2: hits[k], n: n, pct: pct });
}).join('');
console.log('Cache eviction results:', hits, '| seq:', seq.join(','));
}
document.querySelectorAll('.preset').forEach(btn => {
btn.addEventListener('click', () => {
const seqStr = btn.dataset.seq;
document.getElementById('seq').value = seqStr;
render(parseSeq(seqStr));
});
});
document.getElementById('run').addEventListener('click', () => {
render(parseSeq(document.getElementById('seq').value));
});
// Run default
render(parseSeq(document.getElementById('seq').value));
Comments
Loading comments...