<p class="hint">A cache of size <b>k = 3</b>. At each request, <b>LRU</b> evicts the page
unused longest; <b>OPT</b> (clairvoyant) evicts the page whose next use is furthest away.
Step through and watch the page faults. OPT can see the future — LRU cannot.</p>
<div class="streams" id="streams"></div>
<div class="tape" id="tape"></div>
<div class="panes">
<div class="pane"><div class="phead">LRU <span class="sub">least recently used</span></div>
<div class="slots" id="lruSlots"></div><div class="faults" id="lruF">faults: 0</div></div>
<div class="pane"><div class="phead">OPT <span class="sub">clairvoyant (Belady)</span></div>
<div class="slots" id="optSlots"></div><div class="faults" id="optF">faults: 0</div></div>
</div>
<div class="ratio" id="ratio"></div>
<div class="btns">
<button id="step" type="button">Step ▶</button>
<button id="play" type="button">Auto-play</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.streams { display: flex; gap: .4rem; margin-bottom: .6rem; flex-wrap: wrap; }
.streams button { font: 600 13px system-ui; padding: .3rem .7rem; border: 1px solid #adb1b8;
background: #fff; color: #1d3557; border-radius: 999px; cursor: pointer; }
.streams button.active { background: #1d3557; color: #fff; border-color: #1d3557; }
.tape { display: flex; gap: 4px; flex-wrap: wrap; margin: .3rem 0 .8rem; }
.req { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; border-radius: 6px; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; }
.req.cur { background: #ffd166; border-color: #e0ad33; }
.req.done { opacity: .45; }
.panes { display: flex; gap: 1rem; flex-wrap: wrap; }
.pane { flex: 1 1 200px; }
.phead { font: 700 15px system-ui; color: #1d3557; margin-bottom: .35rem; }
.phead .sub { font: 400 11px system-ui; color: #777; }
.slots { display: flex; gap: 6px; }
.slot { width: 46px; height: 46px; display: flex; align-items: center; justify-content: center;
font: 700 18px ui-monospace, monospace; border-radius: 8px; background: #f4f6f8;
border: 2px dashed #cdd9e3; color: #1d3557; transition: all .15s; }
.slot.full { background: #d7e3ec; border-style: solid; border-color: #9fb2c8; }
.slot.hit { background: #b7e4c7; border-color: #0a7d33; }
.slot.miss { background: #f6c6cb; border-color: #c92f3c; }
.faults { margin-top: .4rem; font: 600 14px system-ui; color: #444; }
.ratio { margin: .7rem 0 .3rem; font: 600 14px system-ui; min-height: 1.3em; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .4rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Online paging: cache size k. On a miss with a full cache, choose a victim.
// - LRU evicts the page least-recently used (only past info).
// - OPT (Belady) evicts the page whose NEXT use is furthest in the future (needs the whole sequence). OPT is provably the minimum-fault policy.
// Sleator & Tarjan (1985): LRU never exceeds k * OPT faults, and no deterministic online policy can do better. Here k = 3.
const K = 3;
const STREAMS = {
typical: [1,2,3,4,1,2,5,1,2,3,4,5],
adversary: [1,2,3,4,1,2,3,4,1,2,3,4], // cycles k+1 distinct pages: worst case for LRU
locality: [1,1,2,2,3,1,2,4,4,1,2,5]
};
let seq = STREAMS.typical, name = 'typical', i = 0;
let lru = [], lruLast = {}, lruF = 0; // lru: pages in cache; lruLast: last-use time
let opt = [], optF = 0;
function buildStreams(){
const wrap = document.getElementById('streams'); wrap.innerHTML = '';
Object.keys(STREAMS).forEach(n => {
const b = document.createElement('button'); b.textContent = n;
if (n === name) b.className = 'active';
b.onclick = () => { name = n; seq = STREAMS[n]; reset(); };
wrap.appendChild(b);
});
}
function nextUse(page, from){ // index of next request for `page` at or after `from`
for (let j = from; j < seq.length; j++) if (seq[j] === page) return j;
return Infinity;
}
// Apply one request to LRU. Returns 'hit' | 'miss'.
function stepLRU(t, page){
if (lru.includes(page)) { lruLast[page] = t; return 'hit'; }
lruF++;
if (lru.length >= K){
let victim = lru[0];
for (const p of lru) if (lruLast[p] < lruLast[victim]) victim = p;
lru = lru.filter(p => p !== victim);
}
lru.push(page); lruLast[page] = t; return 'miss';
}
// Apply one request to OPT. Returns 'hit' | 'miss'.
function stepOPT(t, page){
if (opt.includes(page)) return 'hit';
optF++;
if (opt.length >= K){
let victim = opt[0], far = nextUse(opt[0], t+1);
for (const p of opt){ const nu = nextUse(p, t+1); if (nu > far){ far = nu; victim = p; } }
opt = opt.filter(p => p !== victim);
}
opt.push(page); return 'miss';
}
function slotsHTML(arr, mark, markType){
const el = []; for (let s = 0; s < K; s++){
const p = arr[s];
let cls = 'slot' + (p !== undefined ? ' full' : '');
if (p !== undefined && p === mark) cls += ' ' + markType;
el.push(`<div class="${cls}">${p !== undefined ? p : ''}</div>`);
}
return el.join('');
}
function renderTape(){
const t = document.getElementById('tape'); t.innerHTML = '';
seq.forEach((p, j) => {
const d = document.createElement('div');
d.className = 'req' + (j === i ? ' cur' : j < i ? ' done' : '');
d.textContent = p; t.appendChild(d);
});
}
function render(lruMark, lruType, optMark, optType){
renderTape();
document.getElementById('lruSlots').innerHTML = slotsHTML(lru, lruMark, lruType);
document.getElementById('optSlots').innerHTML = slotsHTML(opt, optMark, optType);
document.getElementById('lruF').textContent = fmt('faults: %lruF%', { lruF: lruF });
document.getElementById('optF').textContent = fmt('faults: %optF%', { optF: optF });
const r = optF ? (lruF / optF).toFixed(2) : '—';
const done = i >= seq.length;
document.getElementById('ratio').textContent = done
? fmt('Done. LRU/OPT ratio = %r% (theory guarantees ≤ k = 3).', { r: r })
: fmt('LRU/OPT so far = %r%', { r: r });
}
function step(){
if (i >= seq.length) return;
const page = seq[i];
const lruRes = stepLRU(i, page);
const optRes = stepOPT(i, page);
i++;
render(page, lruRes, page, optRes);
}
let timer = null;
function play(){
if (timer){ clearInterval(timer); timer = null; document.getElementById('play').textContent = 'Auto-play'; return; }
document.getElementById('play').textContent = 'Pause';
timer = setInterval(() => { if (i >= seq.length){ clearInterval(timer); timer=null; document.getElementById('play').textContent='Auto-play'; } else step(); }, 650);
}
function reset(){
if (timer){ clearInterval(timer); timer = null; document.getElementById('play').textContent = 'Auto-play'; }
i = 0; lru = []; lruLast = {}; lruF = 0; opt = []; optF = 0;
buildStreams(); render();
}
document.getElementById('step').onclick = step;
document.getElementById('play').onclick = play;
document.getElementById('reset').onclick = reset;
reset();
<p class="hint">A cache of size <b>k = 3</b>. At each request, <b>LRU</b> evicts the page
unused longest; <b>OPT</b> (clairvoyant) evicts the page whose next use is furthest away.
Step through and watch the page faults. OPT can see the future — LRU cannot.</p>
<div class="streams" id="streams"></div>
<div class="tape" id="tape"></div>
<div class="panes">
<div class="pane"><div class="phead">LRU <span class="sub">least recently used</span></div>
<div class="slots" id="lruSlots"></div><div class="faults" id="lruF">faults: 0</div></div>
<div class="pane"><div class="phead">OPT <span class="sub">clairvoyant (Belady)</span></div>
<div class="slots" id="optSlots"></div><div class="faults" id="optF">faults: 0</div></div>
</div>
<div class="ratio" id="ratio"></div>
<div class="btns">
<button id="step" type="button">Step ▶</button>
<button id="play" type="button">Auto-play</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.streams { display: flex; gap: .4rem; margin-bottom: .6rem; flex-wrap: wrap; }
.streams button { font: 600 13px system-ui; padding: .3rem .7rem; border: 1px solid #adb1b8;
background: #fff; color: #1d3557; border-radius: 999px; cursor: pointer; }
.streams button.active { background: #1d3557; color: #fff; border-color: #1d3557; }
.tape { display: flex; gap: 4px; flex-wrap: wrap; margin: .3rem 0 .8rem; }
.req { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; border-radius: 6px; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; }
.req.cur { background: #ffd166; border-color: #e0ad33; }
.req.done { opacity: .45; }
.panes { display: flex; gap: 1rem; flex-wrap: wrap; }
.pane { flex: 1 1 200px; }
.phead { font: 700 15px system-ui; color: #1d3557; margin-bottom: .35rem; }
.phead .sub { font: 400 11px system-ui; color: #777; }
.slots { display: flex; gap: 6px; }
.slot { width: 46px; height: 46px; display: flex; align-items: center; justify-content: center;
font: 700 18px ui-monospace, monospace; border-radius: 8px; background: #f4f6f8;
border: 2px dashed #cdd9e3; color: #1d3557; transition: all .15s; }
.slot.full { background: #d7e3ec; border-style: solid; border-color: #9fb2c8; }
.slot.hit { background: #b7e4c7; border-color: #0a7d33; }
.slot.miss { background: #f6c6cb; border-color: #c92f3c; }
.faults { margin-top: .4rem; font: 600 14px system-ui; color: #444; }
.ratio { margin: .7rem 0 .3rem; font: 600 14px system-ui; min-height: 1.3em; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .4rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Online paging: cache size k. On a miss with a full cache, choose a victim.
// - LRU evicts the page least-recently used (only past info).
// - OPT (Belady) evicts the page whose NEXT use is furthest in the future (needs the whole sequence). OPT is provably the minimum-fault policy.
// Sleator & Tarjan (1985): LRU never exceeds k * OPT faults, and no deterministic online policy can do better. Here k = 3.
const K = 3;
const STREAMS = {
typical: [1,2,3,4,1,2,5,1,2,3,4,5],
adversary: [1,2,3,4,1,2,3,4,1,2,3,4], // cycles k+1 distinct pages: worst case for LRU
locality: [1,1,2,2,3,1,2,4,4,1,2,5]
};
let seq = STREAMS.typical, name = 'typical', i = 0;
let lru = [], lruLast = {}, lruF = 0; // lru: pages in cache; lruLast: last-use time
let opt = [], optF = 0;
function buildStreams(){
const wrap = document.getElementById('streams'); wrap.innerHTML = '';
Object.keys(STREAMS).forEach(n => {
const b = document.createElement('button'); b.textContent = n;
if (n === name) b.className = 'active';
b.onclick = () => { name = n; seq = STREAMS[n]; reset(); };
wrap.appendChild(b);
});
}
function nextUse(page, from){ // index of next request for `page` at or after `from`
for (let j = from; j < seq.length; j++) if (seq[j] === page) return j;
return Infinity;
}
// Apply one request to LRU. Returns 'hit' | 'miss'.
function stepLRU(t, page){
if (lru.includes(page)) { lruLast[page] = t; return 'hit'; }
lruF++;
if (lru.length >= K){
let victim = lru[0];
for (const p of lru) if (lruLast[p] < lruLast[victim]) victim = p;
lru = lru.filter(p => p !== victim);
}
lru.push(page); lruLast[page] = t; return 'miss';
}
// Apply one request to OPT. Returns 'hit' | 'miss'.
function stepOPT(t, page){
if (opt.includes(page)) return 'hit';
optF++;
if (opt.length >= K){
let victim = opt[0], far = nextUse(opt[0], t+1);
for (const p of opt){ const nu = nextUse(p, t+1); if (nu > far){ far = nu; victim = p; } }
opt = opt.filter(p => p !== victim);
}
opt.push(page); return 'miss';
}
function slotsHTML(arr, mark, markType){
const el = []; for (let s = 0; s < K; s++){
const p = arr[s];
let cls = 'slot' + (p !== undefined ? ' full' : '');
if (p !== undefined && p === mark) cls += ' ' + markType;
el.push(`<div class="${cls}">${p !== undefined ? p : ''}</div>`);
}
return el.join('');
}
function renderTape(){
const t = document.getElementById('tape'); t.innerHTML = '';
seq.forEach((p, j) => {
const d = document.createElement('div');
d.className = 'req' + (j === i ? ' cur' : j < i ? ' done' : '');
d.textContent = p; t.appendChild(d);
});
}
function render(lruMark, lruType, optMark, optType){
renderTape();
document.getElementById('lruSlots').innerHTML = slotsHTML(lru, lruMark, lruType);
document.getElementById('optSlots').innerHTML = slotsHTML(opt, optMark, optType);
document.getElementById('lruF').textContent = fmt('faults: %lruF%', { lruF: lruF });
document.getElementById('optF').textContent = fmt('faults: %optF%', { optF: optF });
const r = optF ? (lruF / optF).toFixed(2) : '—';
const done = i >= seq.length;
document.getElementById('ratio').textContent = done
? fmt('Done. LRU/OPT ratio = %r% (theory guarantees ≤ k = 3).', { r: r })
: fmt('LRU/OPT so far = %r%', { r: r });
}
function step(){
if (i >= seq.length) return;
const page = seq[i];
const lruRes = stepLRU(i, page);
const optRes = stepOPT(i, page);
i++;
render(page, lruRes, page, optRes);
}
let timer = null;
function play(){
if (timer){ clearInterval(timer); timer = null; document.getElementById('play').textContent = 'Auto-play'; return; }
document.getElementById('play').textContent = 'Pause';
timer = setInterval(() => { if (i >= seq.length){ clearInterval(timer); timer=null; document.getElementById('play').textContent='Auto-play'; } else step(); }, 650);
}
function reset(){
if (timer){ clearInterval(timer); timer = null; document.getElementById('play').textContent = 'Auto-play'; }
i = 0; lru = []; lruLast = {}; lruF = 0; opt = []; optF = 0;
buildStreams(); render();
}
document.getElementById('step').onclick = step;
document.getElementById('play').onclick = play;
document.getElementById('reset').onclick = reset;
reset();
Notice the asymmetry. OPT is allowed to cheat — it reads the entire future before deciding. LRU only knows the past. Step through and count the faults: OPT is unbeatable but unbuildable, while LRU, knowing nothing of what comes next, still keeps pace within a fixed factor. Try the adversary stream, which is designed to make any online policy stumble.
Comments
Loading comments...