CASE STUDY / 02 / COMPUTER ARCHITECTURE

Same capacity. Different misses.

I wrote a small cache-memory model to find out exactly how memory-address mapping, associativity and replacement choices change observable behaviour.

THE PROJECT IN ONE MINUTE

My engineering work

Implemented a configurable Node.js cache model, repeatable traces and tests for address mapping, associativity and FIFO/LRU replacement.

Inspect first

Eight passing tests and generated results. On the 96-access synthetic conflict trace, the equally sized two-way model hits 94 times versus zero for direct mapping.

Verification boundary

Functional educational software model, not cache RTL, cycle timing or a CPU benchmark. The traces are deliberately synthetic.

01 / THE QUESTION

Why isn't cache size the whole story?

Two caches can have the same capacity and cache-line size, yet return different hit rates for the same address sequence. The missing variable is where a block is allowed to live. A direct-mapped cache assigns one line to each set. A two-way associative cache can keep two competing blocks in a set at once.

I isolated this behaviour using tiny deterministic traces rather than attempting a full processor benchmark. That lets every reported hit and miss be reproduced—and makes the limitations easy to inspect.

02 / ADDRESS MAPPING

Follow the address to its set.

In the experiment below, both caches hold 128 bytes with 16-byte lines. Addresses 0 and 128 belong to different memory blocks but map to the same set. With one way they continually evict each other; with two ways both can remain resident after the first two misses.

CONCEPTUAL SET MAPPINGNOT A PHYSICAL CACHE LAYOUT
DIRECT-MAPPED / SET 0
1 slotBytes 0 or 128

Every alternating access causes a conflict miss.

2-WAY / SET 0
Way 0Block 0
Way 1Block 8

Both blocks fit; later accesses hit.

Byte addresses 0 and 128 are block indices 0 and 8 (16 bytes per block). Both map to set 0 in these caches. Each cache has eight total lines: eight one-line sets when direct-mapped, or four two-line sets when two-way associative.
01

Block index

Divide the byte address by the line size, discarding the offset within the block.

02

Set index

Take the block index modulo the number of sets. Associativity changes the number of sets, even at constant capacity.

03

Tag and replacement

Use the remaining block-index bits as the tag. On a miss, take an unused way or replace the oldest entry under LRU or FIFO.

03 / MEASURED IN THE MODEL

The trace makes the difference visible.

All values below were generated by running the checked-in program against fixed synthetic address sequences, with the same 128-byte capacity and 16-byte line size. The percentages are hit rates, not execution speedups.

Actual generated cache lookup hit rates
TraceAccessesDirect-mapped2-way LRU2-way FIFO
Sequential words12875%75%75%
Competing addresses960%97.92%97.92%
Repeated working set9695.83%95.83%95.83%
Replacement distinction520%40%20%
ONE FIVE-ACCESS TRACE / SOURCE TO RESULT

Why does LRU beat FIFO on the same two-way set?

  1. Input

    Byte addresses 0, 64, 0, 128, 0 all map to set zero in the 128-byte, 16-byte-line, two-way model. The third access revisits block zero.

  2. Decision

    That hit makes block zero recently used for LRU, but does not change its insertion order for FIFO. When block 128 arrives, LRU evicts block 64; FIFO evicts block zero.

  3. Measured model output

    The last access to zero hits under LRU but misses under FIFO. The generated five-access dataset reports 2/5 LRU hits versus 1/5 FIFO hits. This contrasts replacement rules only; it measures no memory latency or CPU speedup.

Input trace ↗Expected-outcome assertion ↗Generated result data ↗

Plain typeset excerpt of genuine Node command output showing 96 alternating accesses between byte addresses 0 and 128, with 0 of 96 direct-mapped hits, 94 of 96 two-way hits, and eight passing tests.
Actual Node output, without the infographic. This restrained transcript is generated directly from passing shell pipelines, not a photograph of a terminal or a CPU performance benchmark. Rebuild it with python3 scripts/render_cache_terminal.py. See full-size output ↗

Interpretation: associativity helps in the deliberately conflicting trace; the sequential trace already benefits from 16-byte lines. In the five-access replacement trace, revisiting a block updates LRU but not FIFO ordering. None of these experiments predicts speed on a real CPU.

AUTOMATED VERIFICATION

8 / 8 tests passed

Node's built-in test runner checks address extraction, hit/miss outcomes, associativity, replacement order, input validation and replay determinism. A fixed-seed, 800-access test cross-checks eight configurations against a separate reference implementation.

04 / REPRODUCE THE WORK

Not a screenshot. Working code.

The site publishes readable copies of the original cache model, tests, experiment generator and result dataset. The backing portfolio GitHub repository currently requires access, so the links below use public, release-matched source snapshots instead of an inaccessible GitHub file viewer.

RUN LOCALLY · NODE.JS 20+Full instructions ↗
node --test projects/cache-lab/cache.test.mjs
node projects/cache-lab/experiments.mjs
# Add --write to regenerate results.json

Experiments are deterministic. No external services, API keys or dependencies are needed for the simulation.

Download the runnable cache source and tests ↓ (original folder names preserved; Node.js 20+). The model is public educational JavaScript, not CPU cache RTL.

05 / WHAT COMES NEXT

A good model makes its boundaries clear.

This is a small functional teaching model, not synthesizable SystemVerilog and not a cycle-accurate simulation. It does not implement stores, dirty lines, coherence, prefetching, memory latency or pipeline behaviour. The traces are artificial by design, not application benchmarks.

Possible next steps are a write-policy model, real-world traces and then a small RTL cache-controller experiment. Those remain plans, not completed work.

What interested me most was how a simple design choice became a measurable and testable architectural tradeoff.

Discuss the project