perf: benchmark, cache-flush hook & budget (#94 / #95, #96) #474

Merged
manlycucumber merged 1 commit from feature/94-performance into develop 2026-07-10 22:14:10 +00:00
manlycucumber commented 2026-07-10 22:12:39 +00:00 (Migrated from github.com)

What & why

Epic #94 — performance hardening. Resolution was already fast — the caches exist (Corpus parses each file once per process; resolvedYear() memoises per year/calendar/edition; PaschalSkeleton caches its offset table) — so this epic makes that performance measurable, invalidatable, and proven byte-stable, rather than adding speculative optimisation.

#95 — benchmark + budget

bin/benchmark.php measures the four paths the Api exercises at volume against a documented budget (docs/performance.md). Measured on a dev machine (median of 5, leap year 2024):

Path Typical Budget
cold-day (corpus parse + year build + serialise) ~13 ms 1500 ms
warm-day (cache hit) ~0.01 ms 5 ms
full-year (366 days) ~17 ms 4000 ms
year-range (per year, corpus parsed once) ~15 ms 1500 ms

Far under budget with headroom for slow runners. The year-range figure staying flat proves the process-wide corpus cache is reused. Not a CI gate (shared-runner timing is noisy) — byte-stability is what CI enforces.

#96 — cache-loaded-once + invalidation + correctness

  • "Corpus loaded once per process" and "per-year skeleton cached" were already true; documented in docs/performance.md.
  • New Corpus::flush() — drops the process-wide parsed-corpus caches; the data-version invalidation hook for a long-lived process (the Api otherwise rolls versions by starting fresh workers).
  • CacheConsistencyTest resolves a full year warm, calls flush(), resolves it cold, and asserts the two are byte-identical (sha256 over every day's contract) for 1962 and 1954 — proving the cache never changes output.

Verification

  • lint · analyse · test 1030 tests (+3). The full suite passing after the flush test runs confirms flush() breaks nothing. php bin/benchmark.php exit 0.
  • No resolution-behaviour change: flush() is a new additive method; goldens byte-identical.

Closes #95, closes #96.

## What & why Epic **#94 — performance hardening**. Resolution was already fast — the caches exist (Corpus parses each file once per process; `resolvedYear()` memoises per year/calendar/edition; `PaschalSkeleton` caches its offset table) — so this epic makes that performance **measurable, invalidatable, and proven byte-stable**, rather than adding speculative optimisation. ## #95 — benchmark + budget `bin/benchmark.php` measures the four paths the Api exercises at volume against a documented budget (`docs/performance.md`). Measured on a dev machine (median of 5, leap year 2024): | Path | Typical | Budget | | --- | --- | --- | | cold-day (corpus parse + year build + serialise) | ~13 ms | 1500 ms | | warm-day (cache hit) | ~0.01 ms | 5 ms | | full-year (366 days) | ~17 ms | 4000 ms | | year-range (per year, corpus parsed once) | ~15 ms | 1500 ms | Far under budget with headroom for slow runners. The `year-range` figure staying flat proves the process-wide corpus cache is reused. **Not** a CI gate (shared-runner timing is noisy) — byte-stability is what CI enforces. ## #96 — cache-loaded-once + invalidation + correctness - "Corpus loaded once per process" and "per-year skeleton cached" were already true; documented in `docs/performance.md`. - New `Corpus::flush()` — drops the process-wide parsed-corpus caches; the **data-version invalidation hook** for a long-lived process (the Api otherwise rolls versions by starting fresh workers). - `CacheConsistencyTest` resolves a full year warm, calls `flush()`, resolves it cold, and asserts the two are **byte-identical** (sha256 over every day's contract) for 1962 and 1954 — proving the cache never changes output. ## Verification - `lint` ✅ · `analyse` ✅ · `test` ✅ **1030 tests** (+3). The full suite passing *after* the flush test runs confirms `flush()` breaks nothing. `php bin/benchmark.php` ✅ exit 0. - No resolution-behaviour change: `flush()` is a new additive method; **goldens byte-identical**. Closes #95, closes #96.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Directorium/Core!474
No description provided.