codex/fifo-belady-anomaly-frame-comparison.py

fifo-belady-anomaly-frame-comparison.py
"""Generate fifo-belady-anomaly-frame-comparison.png in the working directory.

Tested with Python 3.14.4, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media with MPLBACKEND=Agg. Original state and count comparison.
"""

from collections import deque
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

TRACE = (1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5)


def fifo(capacity):
    """Queue-based implementation, separate from the fixed-slot trace generator."""
    queue = deque()
    rows, misses = [], 0
    for page in TRACE:
        hit = page in queue
        if not hit:
            misses += 1
            if len(queue) == capacity:
                queue.popleft()
            queue.append(page)
        rows.append(dict(queue=tuple(queue), hit=hit, misses=misses))
    return rows


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
    fig, (top, bottom) = plt.subplots(2, 1, figsize=(12, 7.2), dpi=100,
                                     gridspec_kw={"height_ratios": [1, 1.1]})
    fig.patch.set_facecolor("white")
    fig.suptitle("FIFO: one more frame produces one more miss", fontsize=17, y=.975)
    top.set_xlim(-1.8, 12)
    top.set_ylim(6.4, -.7)
    top.axis("off")
    rows3, rows4 = fifo(3), fifo(4)
    labels = ["Access index", "Reference", "3-frame queue", "Outcome", "4-frame queue", "Outcome"]
    for y, label in enumerate(labels):
        top.text(-.14, y, label, ha="right", va="center", fontsize=10)
    for i, (a, b) in enumerate(zip(rows3, rows4)):
        values = [str(i + 1), str(TRACE[i]), "".join(map(str, a["queue"])), "H" if a["hit"] else "M",
                  "".join(map(str, b["queue"])), "H" if b["hit"] else "M"]
        for y, value in enumerate(values):
            fill = "#eaf1f8" if y in (0, 1) else "white"
            if y in (3, 5):
                hit = a["hit"] if y == 3 else b["hit"]
                fill = "#d9eee5" if hit else "#f8dfd7"
            top.add_patch(Rectangle((i, y - .43), .99, .86, facecolor=fill,
                                    edgecolor="#ccd2d8", linewidth=.6))
            top.text(i + .5, y, value, ha="center", va="center", fontsize=11)
    top.text(5.1, 6.05, "Queue order: oldest → newest. Digits are separate page labels, not addresses.",
             ha="center", fontsize=10)
    for rows, label, color in [(rows3, "3 frames: 9 misses", "#167b9a"),
                               (rows4, "4 frames: 10 misses", "#b64430")]:
        bottom.step(range(13), [0] + [r["misses"] for r in rows], where="post",
                    color=color, linewidth=2, marker="o", label=label)
    bottom.set(xlim=(0, 12.3), ylim=(0, 11), xlabel="Number of references processed",
               ylabel="Cumulative misses", xticks=range(13), yticks=range(0, 11, 2))
    bottom.grid(alpha=.2)
    bottom.spines[["top", "right"]].set_visible(False)
    bottom.legend(loc="upper left", frameon=False)
    bottom.annotate("After reference 7: page 1 is in the 3-frame set\nbut absent from the 4-frame set.",
                    xy=(7, 7), xytext=(6.3, 2.5), fontsize=10,
                    arrowprops={"arrowstyle": "->", "color": "#52616b"})
    fig.text(.5, .02, "Identical trace and empty start; hits do not refresh FIFO arrival order.",
             ha="center", fontsize=11)
    fig.subplots_adjust(left=.13, right=.975, top=.9, bottom=.13, hspace=.35)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()