Skip to main content

Bélády's Anomaly: Numerical Proof with 3 vs. 4 Frames

Medium

Interview Question: "Prove Bélády's Anomaly using the reference string (1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5) comparing 3 frames against 4 frames under FIFO. Why do LRU and Optimal algorithms never exhibit this anomaly? Formulate your answer around Mattson's Stack Algorithm Inclusion Property."


The surprising result​

More RAM should mean fewer page faults, never more. In 1969, László Bélády showed that's not always true — under FIFO specifically, adding an extra frame can make things worse. Same reference string, two frame counts:

3 frames
9 faults, 3 hits
75% fault rate
4 frames — more memory
10 faults, 2 hits
83.3% fault rate — worse, despite more RAM

That's Bélády's Anomaly: giving FIFO more memory just handed it a worse eviction order.


Part 1: FIFO with 3 Frames​

Under FIFO, memory behaves as a strict queue. The page residing in physical memory the longest is victimized first, irrespective of its frequency or recency of access.

Step-by-Step Simulation (3 Frames)​

StepPage ReqFrame 1Frame 2Frame 3FIFO Queue (Oldest -> Newest)ResultEvicted Victim
111--[1]❌ Fault-
2212-[1, 2]❌ Fault-
33123[1, 2, 3]❌ Fault-
44423[2, 3, 4]❌ Fault1
51413[3, 4, 1]❌ Fault2
62412[4, 1, 2]❌ Fault3
75512[1, 2, 5]❌ Fault4
81512[1, 2, 5]✅ Hit-
92512[1, 2, 5]✅ Hit-
103532[2, 5, 3]❌ Fault1
114534[5, 3, 4]❌ Fault2
125534[5, 3, 4]✅ Hit-
  • Total Page Faults (3 Frames): 9
  • Total Page Hits (3 Frames): 3
  • Fault Rate: 9 / 12 = 75.0%

Part 2: FIFO with 4 Frames​

Now expand physical memory to 4 frames. Intuitively, having an extra frame should allow the working set to fit better. Let us trace the identical reference string:

Step-by-Step Simulation (4 Frames)​

StepPage ReqFrame 1Frame 2Frame 3Frame 4FIFO Queue (Oldest -> Newest)ResultEvicted Victim
111---[1]❌ Fault-
2212--[1, 2]❌ Fault-
33123-[1, 2, 3]❌ Fault-
441234[1, 2, 3, 4]❌ Fault-
511234[1, 2, 3, 4]✅ Hit-
621234[1, 2, 3, 4]✅ Hit-
755234[2, 3, 4, 5]❌ Fault1
815134[3, 4, 5, 1]❌ Fault2
925124[4, 5, 1, 2]❌ Fault3
1035123[5, 1, 2, 3]❌ Fault4
1145124[1, 2, 3, 4]❌ Fault5
1255124[2, 3, 4, 5]❌ Fault1
  • Total Page Faults (4 Frames): 10
  • Total Page Hits (4 Frames): 2
  • Fault Rate: 10 / 12 = 83.3%

Side-by-Side: where the two runs diverge​

Comparative Step Matrix​

StepRequest3-Frame State3-Frame Result4-Frame State4-Frame ResultDivergence Explanation
1–41, 2, 3, 434 Faults44 Faults3-frame evicted 1 at step 4; 4-frame kept all 4 pages.
513❌ Fault4✅ Hit4-frame benefited from extra capacity.
622❌ Fault4✅ Hit4-frame is temporarily ahead (2 faults vs. 0 in steps 5-6).
752❌ Fault4❌ FaultThe Turning Point: In 4 frames, page 1 was oldest -> evicted. In 3 frames, 1 reloaded at step 5 -> retained!
812✅ Hit4❌ Fault3-frame hits 1; 4-frame faults and evicts 2.
922✅ Hit4❌ Fault3-frame hits 2; 4-frame cascades into another fault.
1032❌ Fault3❌ FaultBoth fault.
1144❌ Fault4❌ FaultBoth fault.
1254✅ Hit4❌ Fault3-frame retained 5; 4-frame evicted 5 at step 11 -> faults!

Why Did the 4-Frame System Cascade?​

At step 4, the 3-frame system was forced to evict page 1. Consequently, when page 1 was referenced again at step 5, it was brought back into memory with a fresh arrival timestamp.

In the 4-frame system, page 1 was never evicted at step 4; its original arrival timestamp at step 1 remained unchanged. Because FIFO ignores hits and does not refresh timestamps, page 1 became the oldest page in the 4-frame system and was promptly evicted at step 7 right before it was needed again at step 8. This caused a catastrophic cascade of faults in steps 8, 9, 11, and 12.


The proof: Mattson's Stack Algorithms​

To understand why LRU and Optimal are immune to Bélády's Anomaly, we look at the theoretical framework introduced by Mattson et al. (1970).

Definition: Inclusion Property​

Let B(t,m)B(t, m) denote the set of memory pages held in physical frames at time tt for a system allocated mm frames:

∀t,∀m:B(t,m)⊆B(t,m+1)\forall t, \forall m: \quad B(t, m) \subseteq B(t, m+1)

An algorithm is classified as a Stack Algorithm if and only if it satisfies this Inclusion Property for all reference strings.

Consequence on Page Faults​

If an algorithm satisfies the inclusion property, every page reference rtr_t that produces a cache hit in an mm-frame system is guaranteed to be present in an (m+1)(m+1)-frame system:

rt∈B(t,m)  ⟹  rt∈B(t,m+1)r_t \in B(t, m) \implies r_t \in B(t, m+1)

Therefore, any hit in an mm-frame configuration is necessarily a hit in an (m+1)(m+1)-frame configuration. Page faults can only decrease or remain constant as frame count grows:

F(m+1)≤F(m)F(m+1) \le F(m)

Why LRU is a Stack Algorithm​

At any step tt, the state of LRU can be modeled as a stack St=[p1,p2,…,pk]S_t = [p_1, p_2, \dots, p_k] ordered by recency of reference (p1p_1 is most recently referenced, pkp_k is least recently referenced):

  • For an mm-frame system, B(t,m)={p1,p2,…,pm}B(t, m) = \{p_1, p_2, \dots, p_m\}.
  • For an (m+1)(m+1)-frame system, B(t,m+1)={p1,p2,…,pm,pm+1}B(t, m+1) = \{p_1, p_2, \dots, p_m, p_{m+1}\}.

Evidently: B(t,m)⊂B(t,m+1)B(t, m) \subset B(t, m+1)

The set of pages in mm frames is always a strict subset of (m+1)(m+1) frames. Hence, LRU can never exhibit Bélády's Anomaly.

Why Optimal (MIN) is a Stack Algorithm​

Optimal replacement evicts the page whose next request occurs furthest in the future. At time tt, pages are ranked by forward distance to next reference. An mm-frame system retains the top mm pages with the smallest forward distances; an (m+1)(m+1)-frame system retains the top m+1m+1. Since the top mm elements are identical in both, the inclusion property holds strictly.

Formal Proof: FIFO Violates the Inclusion Property​

To prove FIFO is not a stack algorithm, a single counterexample demonstrating B(t,m)⊈B(t,m+1)B(t, m) \not\subseteq B(t, m+1) suffices.

From our numerical trace at step t=7t = 7 (after inserting page 5):

  • Memory state with m=3m = 3 frames: B(7,3)={1,2,5}B(7, 3) = \{1, 2, 5\}
  • Memory state with m=4m = 4 frames: B(7,4)={2,3,4,5}B(7, 4) = \{2, 3, 4, 5\}

Observe that: 1∈B(7,3)but1∉B(7,4)  ⟹  B(7,3)⊈B(7,4)1 \in B(7, 3) \quad \text{but} \quad 1 \notin B(7, 4) \implies B(7, 3) \not\subseteq B(7, 4)

Because page 1 was in B(7,3)B(7, 3) but missing from B(7,4)B(7, 4), reference r8=1r_8 = 1 resulted in a hit in 3 frames but a fault in 4 frames, violating monotonicity.


Which algorithms are vulnerable​

AlgorithmStack Algorithm?Satisfies Inclusion Property?Immune to Bélády's Anomaly?Reason / Eviction Basis
Optimal (MIN)YesYesImmuneEvicts furthest future reference.
LRUYesYesImmuneEvicts longest past reference distance.
LFU (with recency tie-breaker)YesYesImmuneOrdered by reference frequency and recency.
FIFONoNoVulnerableEvicts purely by arrival timestamp.
Second Chance / ClockNoNoVulnerableApproximates FIFO with reference bits; queue order varies with capacity.
Random ReplacementNoNoVulnerableEviction is stochastic; no subset guarantee.

Interview traps​

Trap 1: "Does increasing frames under FIFO always increase page faults?"
Candidate Answer: "No. Bélády's anomaly is an anomaly, not a rule. In most practical strings, increasing frames decreases or maintains page faults. It only increases faults for specific pathological sequences where queue ordering misaligns."

Trap 2: "Can Bélády's anomaly occur in modern CPU L1/L2/L3 hardware caches?"
Candidate Answer: "Hardware caches almost universally use true LRU, pseudo-LRU (tree-PLRU), or bit-PLRU replacement policies. Because LRU is a stack algorithm, it is mathematically immune to Bélády's anomaly. Furthermore, hardware caches are typically set-associative rather than fully associative FIFO queues."

Trap 3: "Is Second-Chance (Clock) immune because it checks the reference bit?"
Candidate Answer: "No. Second-chance is fundamentally an enhancement of FIFO. The circular pointer advances according to FIFO arrival times. Adding frames alters pointer alignment and can trigger Bélády's anomaly."


Python: simulate it and check the proof​

The following standalone script simulates FIFO and LRU page replacement policies across variable frame counts, proves the anomaly mathematically, and checks the Mattson inclusion property:

"""
Belady's Anomaly and Mattson Stack Algorithm Simulator
Proves Belady's Anomaly on FIFO and verifies LRU inclusion property.
"""
from typing import List, Set, Tuple


def simulate_fifo(reference_string: List[int], num_frames: int) -> Tuple[int, List[dict]]:
frames: List[int] = []
faults = 0
history = []

for step, page in enumerate(reference_string, 1):
if page in frames:
result = "HIT"
evicted = None
else:
result = "FAULT"
faults += 1
if len(frames) < num_frames:
frames.append(page)
evicted = None
else:
evicted = frames.pop(0) # Evict oldest
frames.append(page)

history.append({
"step": step,
"page": page,
"frames": set(frames),
"result": result,
"evicted": evicted
})

return faults, history


def simulate_lru(reference_string: List[int], num_frames: int) -> Tuple[int, List[dict]]:
frames: List[int] = [] # Index 0: least recent, Index -1: most recent
faults = 0
history = []

for step, page in enumerate(reference_string, 1):
if page in frames:
result = "HIT"
frames.remove(page)
frames.append(page)
evicted = None
else:
result = "FAULT"
faults += 1
if len(frames) < num_frames:
frames.append(page)
evicted = None
else:
evicted = frames.pop(0)
frames.append(page)

history.append({
"step": step,
"page": page,
"frames": set(frames),
"result": result,
"evicted": evicted
})

return faults, history


def check_inclusion_property(history_m: List[dict], history_m1: List[dict]) -> List[int]:
"""Returns steps where B(t, m) is NOT a subset of B(t, m+1)."""
violations = []
for h_m, h_m1 in zip(history_m, history_m1):
step = h_m["step"]
set_m: Set[int] = h_m["frames"]
set_m1: Set[int] = h_m1["frames"]
if not set_m.issubset(set_m1):
violations.append(step)
return violations


if __name__ == "__main__":
ref_string = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]

print("=" * 68)
print("BELADY'S ANOMALY SIMULATION")
print(f"Reference String: {ref_string}")
print("=" * 68)

print("\n--- Frame Count Analysis (FIFO vs. LRU) ---")
print(f"{'Frames':<8} | {'FIFO Faults':<14} | {'LRU Faults':<12}")
print("-" * 40)
for frames in range(1, 7):
f_fifo, _ = simulate_fifo(ref_string, frames)
f_lru, _ = simulate_lru(ref_string, frames)
anomaly_flag = " <-- ANOMALY!" if frames == 4 and f_fifo > 9 else ""
print(f"{frames:<8} | {f_fifo:<14} | {f_lru:<12}{anomaly_flag}")

# Detailed 3 vs 4 frames FIFO
faults_3, hist_3 = simulate_fifo(ref_string, 3)
faults_4, hist_4 = simulate_fifo(ref_string, 4)

print("\n--- Mattson Inclusion Property Check (FIFO: 3 vs 4 Frames) ---")
violations = check_inclusion_property(hist_3, hist_4)
print(f"Steps where B(t, 3) is NOT a subset of B(t, 4): {violations}")
for step in violations:
set_3 = hist_3[step - 1]["frames"]
set_4 = hist_4[step - 1]["frames"]
diff = set_3 - set_4
print(f" Step {step}: B({step}, 3)={set_3}, B({step}, 4)={set_4} -> Missing elements: {diff}")

# Detailed 3 vs 4 frames LRU
lru_3, hist_lru_3 = simulate_lru(ref_string, 3)
lru_4, hist_lru_4 = simulate_lru(ref_string, 4)
lru_violations = check_inclusion_property(hist_lru_3, hist_lru_4)
print(f"\n--- Mattson Inclusion Property Check (LRU: 3 vs 4 Frames) ---")
print(f"Inclusion violations under LRU: {lru_violations} (Strictly 0 violations)")