Skip to content

dp/page_replacement: update FIFO to PersistentQueue and LRU to index map - #49

Merged
m-hu merged 1 commit into
masterfrom
fix/page-replacement-complexity
Nov 4, 2026
Merged

m-hu merged 1 commit into
masterfrom
fix/page-replacement-complexity

Conversation

@m-hu

@m-hu m-hu commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Updates dp/page_replacement/page_replacement.clj to bring its FIFO and LRU algorithms into complexity parity with its Python twin:

  • FIFO: replaces vector-copying (vec (rest q)) ((f)$ per eviction) with clojure.lang.PersistentQueue ((1)$ amortized peek, pop, and conj). Total FIFO time drops from (m \cdot f)$ to (m)$.
  • LRU: replaces linear scans (some #(= % p) lru) and vector removals ((f)$ per ref) with a timestamp-indexed hash-map p->t and a sorted set of [t p] timestamps. Per-ref operations drop to (\log f)$ amortized.
  • Marking: drives the loop by index i instead of lazy (rest rs) sequences.

Validation

  • All 4 shared test cases in resource/shared/dp/page_replacement/page_replacement pass byte-identically.
  • 50 randomized differential tests against python-code-snippet pass byte-identically across varying $ and $.
  • 80/80 tests pass in recipes pytest suite (problem_test/dp/page_replacement).
  • Large input benchmark (=100,000, f=500$):
    • FIFO elapsed time: ~96 ms
    • LRU elapsed time: ~260 ms

- FIFO: replace O(f) vector-copying (vec (rest q)) with PersistentQueue (O(1) amortized peek/pop/conj).
- LRU: replace O(f) vector scans (some #(= % p) lru) and deletions with timestamp-indexed hash-map + sorted-set (O(log f) amortized per ref).
- Marking: drive loop by index i instead of lazy (rest rs) sequences.

Brings FIFO and LRU asymptotic complexity into parity with the Python twin.
Verified against all 4 shared test cases + 50 randomized differential tests against Python + 80 pytest cases across all languages.
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

🐰 Bencher Report

Projectclojure-code-snippet
Branchfix/page-replacement-complexity
Commit6bb702a
ReportTestbedAdapterStatus
1ubuntu-latest-clojure-rsJSON✅ No Alerts
2ubuntu-latest-jvmJSON✅ No Alerts

⚠️ WARNING: Truncated view!

The full continuous benchmarking report exceeds the maximum length allowed on this platform.

@michael-h-hu michael-h-hu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #49 — dp/page_replacement FIFO/LRU complexity fix

Verdict: no findings — approve

What was inspected

  • Full diff of src/dp/page_replacement/page_replacement.clj (only file changed)
    plus the pre-image from master for old-vs-new comparison.
  • gh pr checks 49: every check green (test shards 1–4, perf-callgrind
    jvm + clojure-rs legs, report, resolve-rs).
  • No call sites: snippet is a standalone stdin/stdout program; no other
    repo code depends on its internals.

Correctness

  • FIFO: q [] + (vec (rest q)) eviction replaced by
    clojure.lang.PersistentQueue/EMPTY with peek/pop/conj, keeping
    the s membership set. Queue/set stay in lockstep; pop/peek are
    only reached when (count q) >= f with f > 0, so never on an empty
    queue. f <= 0 short-circuit preserved.
  • LRU: vector scan/remove replaced by p->t map plus sorted-set of
    [t p] with strictly increasing timestamps, so ordering is total and
    (first t-set) is always the least-recently-used page. Map and set are
    updated together on hits, misses, and evictions; eviction branch is only
    reachable with a non-empty set. f <= 0 short-circuit preserved.
  • Marking loop: same eviction logic, only the driver changed from lazy
    (rest rs) to index i over rq — same sequence, no lazy-seq
    retention.
  • Java interop is minimal and idiomatic: only the
    PersistentQueue/EMPTY constant; all ops are core fns. sorted-set
    keeps LRU purely functional instead of reaching for a mutable
    LinkedHashMap.

Complexity

  • FIFO: O(m·f) → O(m) time (peek/pop/conj/count all O(1)
    amortized on PersistentQueue); space stays O(f).
  • LRU: O(m·f) → O(m·log f) time (map ops plus sorted-set
    conj/disj/first); space stays O(f). O(log f) rather than the
    Python twin's O(1) is the right tradeoff for a purely functional,
    low-interop implementation.
  • Marking: unchanged asymptotics; index driving only trims constant
    overhead.

Verification run

  • 26 hand-built old-vs-new differential cases through JVM Clojure
    (basic, classic reference strings, f=0/negative/1, m=0, all-same,
    all-distinct, negative/large page ids, Belady sequences, cyclic and
    thrash patterns, plus 2000- and 5000-ref pseudo-random inputs): all
    byte-identical, including the marking line. Belady's anomaly still
    reproduces (FIFO f=3 faults 9 < f=4 faults 10), confirming FIFO
    semantics survived the refactor.
  • One 20k-ref input: identical output on both versions.

Residual risks / gaps

  • None blocking. Worst case worth noting: none of the differential
    inputs exceed ~20k refs locally, but CI's full gate (incl. perf legs)
    is green on this HEAD.

AI-Tool: Muse Code (Meta Muse Spark)

@m-hu
m-hu merged commit e3834bc into master Nov 4, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants