Skip to content

bench(#190): declare the history corpus reading as a phase, and measure the corpus axis - #204

Open
yifanxuaaa wants to merge 4 commits into
codex/190-admission-qualificationfrom
codex/190-corpus-phase-declaration
Open

yifanxuaaa wants to merge 4 commits into
codex/190-admission-qualificationfrom
codex/190-corpus-phase-declaration

Conversation

@yifanxuaaa

Copy link
Copy Markdown
Contributor

Stacked on #203 (the qualification disposition); this PR's base is that branch so the diff is only this round. 57d761b36.

Executes the first two steps of the plan I put to the owner: declare the corpus reading as a phase, and measure the corpus axis. One sample per selection, retained source (no instrumentation patch), both global flocks held after the quiet preflight, established 120/240 s diagnostic caps. No pin written, no budget class changed, #190 stays open.

The change

Harness only, +180/−5 lines in core/benchmark/fs-bench-pro-storage-content/src; no product line:

  • phases::add_preparation(ns) — attributes a measured span of harness input assembly to the preparation phase, so the four declared phases account for an invocation whose assembly happens between the measured children. The declaration can only make the budgeted command larger.
  • workload::history::ReadProbemincore over a fresh mapping of each corpus file before this invocation first reads it, once per distinct path. Diagnostic only.
  • ops::history — declares the corpus span, begins the probe after preparation closes, publishes the probe and the chain's rusage disk_read_bytes delta.

Workload untouched: same corpus, same states, same measured children, one construction worker, nothing invalidated, no timeout enlarged, no selection shrunk.

Reconciliation: was INCOMPLETE, now PASS

before (retained runs) now
unexplained, stride10 18,103,407,414 ns of 40,220,718,250 25,839,123 ns
unexplained, stride3 23,021,619,251 ns of 73,127,405,000 46,400,935 ns

phases.compose (the runner's own function, imported not reimplemented): PASS for both.

Measured

Quantity stride10 stride3
complete command 37.613 s 75.121 s
preparation (window + declared corpus) 17.766 s 26.033 s
operation 19,738,993,418 ns 48,976,026,830 ns
declared phases summed 37.521 s 75.038 s
budget, ordinary 15 s / exception 25 s NOT_RUN / NOT_RUN NOT_RUN / NOT_RUN
corpus files probed before first read 45,338 (489,820,959 B, 62,319 pages) 78,439 (850,636,296 B, 107,780 pages)
corpus pages resident before first read 0 (0.000%) 4,298 (3.988%)
device bytes read across the chain 603,807,744 977,244,160

Operation is within 0.8% / 1.1% of the retained 19,888,424,711 / 49,501,013,748 ns. Two independent instruments agree on the corpus axis, and the second run shows the first run's leavings: real, measurable, small. Positive control: the Store this run wrote reads back as 3,007/3,011 resident pages through the same mincore method. Not a cold claim — nothing was invalidated and one run per selection establishes no distribution.

Correction to the disposition, by measurement

DISPOSITION §2.3/§2.4 projected the declared commands at 36.3 s / 73.0 s and concluded stride10 would fit a declared 25 s exception. Measured they are 37.521 s / 75.038 s and both exceed 25 s. The projection's arithmetic was right and its input was wrong (the retained runs' corpus readings were 16.2 / 23.0 s; these are 17.6 / 25.5 s). So D4 has three shapes: a new frozen large-history class; a prepared, identity-checked corpus input — the setup-reuse route AGENTS.md §2 requires, projected ~20 s / ~50 s and labelled NOT_MEASURED, motivated by the measured 27.9 MB/s across 45,338 files (388 µs per file: a per-file cost, not a bandwidth cost); or diagnostic only.

Checks and limits

cargo +1.85.1 test 117 tests PASS on this source; release build PASS (sha256 190424195506d4d54b24d253b483986d9aadf542fe125ae61225d833c2102ab1). Harness Clippy's 21 warnings and 1 denied-lint error (never_loop, src/ops/history.rs:1855 at HEAD) are inherited, none in the added lines. The three touched files are rustfmt-clean; 121 pre-existing format diffs elsewhere were reverted rather than swept in. One collector attempt was refused by the child for pre-creating its output directory; it consumed no sample and is retained at runs/refused-history-stride10-exit2/.

Standing rulings respected: no pin written, no value hand-edited, no cold claim, no cache pooled, no selection shrunk, no timeout enlarged, no case re-run, no diagnostic cap promoted, no history row added to registry::cases() / FROZEN_CARDINALITY / the 217-row self-check.

Production LOC: 85,725 → 85,725 (delta 0); reference 65,417, core 20,308. Ledger L45.

docs/roadmap/0.1/0.1.7/evidence/stage-6-history-190-corpus-phase-20260920T054215Z/README.md · analysis · collector

Refs #190.

…us axis

The `history.*` rows declared their phases in a way that could not reconcile: the
harness reads its corpus between the measured children, so 18.1 s (stride10) and
23.0 s (stride3) of the complete command sat outside every declared phase and
`runner.py` failed the reconciliation closed to INCOMPLETE.

`phases::add_preparation` lets a driver attribute a measured span of harness input
assembly to the preparation phase, and `ops::history` declares its corpus reading
there. The declaration can only make the budgeted command larger. Reconciliation now
PASSES: the unexplained remainder is the child's own non-phase work, 25.8 ms and
46.4 ms inside a ~1 s tolerance.

The corpus axis of the cache stance is measured instead of assumed:
`workload::history::ReadProbe` takes `mincore` over a fresh mapping of each corpus
file before this invocation first reads it, once per distinct path, and the driver
publishes that plus the chain's `rusage` disk_read_bytes delta. stride10: 45,338
files, 489,820,959 B, 62,319 pages, **0 resident**, 603,807,744 B from the device.
stride3: 78,439 files, 850,636,296 B, 107,780 pages, **4,298 resident (3.988 %)**,
977,244,160 B from the device. Diagnostics only: nothing fails on them, nothing is
de-warmed, and this is not a cold claim.

Measured: stride10 wall 37.613 s, preparation 17.766 s, operation 19,738,993,418 ns;
stride3 wall 75.121 s, preparation 26.033 s, operation 48,976,026,830 ns. Both
classify NOT_RUN under the ordinary 15 s and the declared 25 s exception, so the
corpus reading being declared corrects L44's projection: the declared commands are
37.521 s / 75.038 s, not 36.3 / 73.0, and stride10 does not fit 25 s.

Checks: 117 harness tests PASS on this source, release build PASS
(sha256 190424195506d4d54b24d253b483986d9aadf542fe125ae61225d833c2102ab1). Harness
Clippy's 21 warnings and 1 denied-lint error are inherited, none in the added lines.
The three touched files are rustfmt-clean; 121 pre-existing format diffs elsewhere
were reverted rather than swept in. Resource commands ran under both global flocks
after the quiet preflight; one collector attempt was refused for pre-creating its
output directory, consumed no sample and is retained.

Production LOC: 85725 -> 85725 (delta 0); reference 65417, core 20308.
Harness +180/-5 lines in core/benchmark/fs-bench-pro-storage-content/src (excluded
from production LOC). Ledger L45.

Refs #190.
history-stride1 is the one history.* row no #190 campaign had sampled. Cap declared
before the sample by the retained convention (~4.5 s of complete command per state):
720 s, never approached. The driver refuses per-state phase nodes above 53 states, so
stride1 ran the ordinary recording.

Measured: wall 199.025 s, preparation 52.664 s (corpus 51.142 s), operation 146.178 s
over 157 states, reconciliation PASS, both budget limits NOT_RUN. Per state: mean
931.1 ms, median 698.5 ms, max 8,952.0 ms. Corpus probe: 180,128 files, 2,933 pages
resident at first read (1.19%), 2,369,449,984 B from the device.

The substantive finding is a depth term. Per-state elapsed tracks changed bytes
(+0.90 to +0.99), but ns per changed byte rises 1.7-2.8x down the chain, and at
matched changed volume the second half of the chain costs 1.22-2.31x the first with
ns per byte roughly doubling. Localised by sub-phase, both product paths carry it:
stride3 filesystem 47.5 -> 843.4 ms/state (17.8x) and storage.accept_loop 78.0 ->
658.3 (8.4x). Bounds stated: within-run, one sample, 2-21 states per band, matched on
changed bytes only, no mechanism attributed.

Store bytes, the one axis where v0.1.6 is directly comparable: stride10 +905,216 above
its target, stride3 -946,176 below, stride1 +593,920 above; on content the core is
below the historical apparent figure on two of three rows. Allocation is not precise:
stride3 reads 62,152,704 then and 63,078,400 now for Stores that hash identically.

Also closed by measurement: the retained source without the instrumentation patch
reproduces the measured candidate's Stores byte for byte (4af37932aa3391b1...,
f5c7ff5a6b4f0821...). The retained source's own timing remains the open gap.

Production LOC: 85725 -> 85725 (delta 0); reference 65417, core 20308. Ledger L46.

Refs #190.
Two axes, measured, and the mechanism behind the second.

More chunks for the same history: 17 states 377.4 changed MB -> 19.739 s; 53 states
713.7 MB -> 48.976 s; 157 states 1,719.8 MB -> 146.178 s. Operation grows 7.41x against
9.24x states, and ns per changed MB rises 63% from the coarsest to the finest split,
because finer chunking writes more total content and every state pays the depth term.

The depth term is per-unit cost, not per-byte work. At matched changed volume, late-half
states cost 1.2-2.3x the early half; provider waves per changed MB fall (0.45-0.80x),
chain edges per record call stay flat (0.33->0.36), pack fetches per wave rise 4.0-4.8x,
KiB per fetch rise 1.3-2.2x, the decoded-group hit rate falls 0.967->0.898, and pack
bytes copied per changed MB rise up to 7.1x (12.1 M -> 86.2 M). Elasticity of filesystem
ns/MB against ln(state): +8,755,936 (stride10), +13,108,101 (stride3). The read path
makes fewer, larger calls as the chain grows - a bounded cache against a growing working
set, stated as the reading the counters point at rather than a measured cache curve.

Consequence: L42's Priority B pack/decoded-cache scope treatment (per-leaf reuse with a
Store-write invalidation contract) is the best-motivated next experiment, and its value
should grow with history length; chunk-selection policy is the largest multiplier in the
table and is a product decision. No automatic cache growth proposed.

Production LOC: 85725 -> 85725 (delta 0). Ledger L47.

Refs #190.
Hands the depth term to the next round: the three-row starting point, the scaling
measurements, the counter evidence that the term is per-unit cost rather than per-byte
work, the two candidate explanations and the experiment that separates them (L42's
Priority B pack/decoded-cache scope with a Store-write invalidation contract), the
Store-byte comparison and the allocation movement measured on identical content, the
v0.1.6 scope correction, and the full protocol and do-not list.

Documentation-only. Production LOC: 85725 -> 85725 (delta 0); reference 65417, core
20308. Ledger L48.

Refs #190.
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.

1 participant