Skip to content

render-pipeline: measure cross-reference invalidation (step 0 of #575; no behaviour change) - #613

Merged
GoKubar merged 2 commits into
mainfrom
agent/daniel-parent/xref-measure-step0
Sep 15, 2026
Merged

GoKubar merged 2 commits into
mainfrom
agent/daniel-parent/xref-measure-step0

Conversation

@d-q222

@d-q222 d-q222 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

What

Step 0 of the incremental cross-references proposal (#575): measure before choosing. No behaviour change and no design decision.

document edit adapted miss / lookups blocks miss / lookups assembled miss / lookups layout passes handle_line ms (median) render ms (median)
535 no-label 0 / 5013 1 / 5461 1 / 5461 1 18493.1 18463.1
535 renumber 4117 / 5015 902 / 5463 902 / 5463 1 17383.1 17354.2
535 move-label 1 / 5013 3 / 5461 3 / 5461 1 16189.0 16162.4
535+toc+pageref no-label 0 / 10026 1 / 10924 1 / 5462 2 34935.8 34902.1
535+toc+pageref renumber 8234 / 10030 1239 / 10928 1236 / 5464 2 35206.4 35172.1
535+toc+pageref move-label 4116 / 10026 4 / 10924 4 / 5462 2 35405.4 35373.7

The cold render takes 18 278 ms for 535 and 34 738 ms for 535+toc+pageref. The cold adapted count is 4 115 misses out of 5 013 lookups per pass, so every distinct block misses.

Why

#575 §5 step 0 asks whether the IDE worker is already fast on these three edits before any option is chosen. Findings:

  1. §2.2's invalidation analysis is confirmed.
    • renumber misses adapted for every distinct block in every pass.
    • move-label misses it for every distinct block in pass 2.
    • Below the adapter, reuse stays selective: 1–4 block misses, or 902–1 239 for renumber.
  2. Warm latency equals cold latency on every edit (16–18 s with one pass, about 35 s with two). The cache misses are not what costs the time.
  3. The time is in adapter::adapt_cached, and it grows quadratically.
    • stages, run warm: adapt takes 2 013 ms at 150 sections and 16 368 ms at 450. Parse plus typeset plus assemble is under 100 ms.
    • Temporary probes (not committed) put about 95% of the adapt time in split_at_page_breaks. Three per-block walks of the source prefix account for it: in_theorem_environment (717 ms at 150 sections; added by 2945f61a), the ListItem branch (831 ms; list_stack_at and others) and the after-list branch (290 ms).
    • This is recorded as a finding for whoever owns adapter.rs, not as a proposal. Until it is fixed, render-pipeline labels_fp narrowing or page seeding could not show a latency win on this document.

Overlap check

Test results

cargo test --test cache_counters
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
cargo test --test incremental
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 375.30s
cargo run --release --example xref_measure -- 5 450
exit=0 (table above; load average 23.30 20.81 19.26 at the start)
generator check against #535's large_doc_bench.rs: identical; 637761 bytes

Not done

🤖 Generated with Claude Code

https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

…es (measurement only)

What changed:
- RenderCache gains plain Cell counters for `adapted` and `assembled`
  hits/misses and a label-pass counter bumped once per render_cached
  layout pass; `counters()` returns them with the existing `blocks`
  stats as `CacheCounters` (`since` for deltas). `stats()` is unchanged.
  Nothing on the render path reads the counters: no behaviour change.
- examples/xref_measure.rs: #535's 450-section article (byte-identical
  generator) plus a +toc+pageref variant, through protocol::handle_line
  with a warm cache; three edits (no-label, renumber, move-label).
- tests/cache_counters.rs: the counters count cold misses, warm hits and
  passes.

What was run:
- cargo test --test cache_counters: test result: ok. 1 passed; 0 failed
- generator check against #535's large_doc_bench.rs: identical; 637761 bytes

Next step: full measurement (5 runs, 450 sections) and the results doc.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…575 step 0)

What changed:
- docs/design/incremental-cross-references-measurements.md: adapted /
  blocks / assembled misses, layout passes and median latency for the
  no-label, renumber and move-label edits on #535's 450-section article
  and a +toc+pageref variant, with the exact commands, plus a stage split
  showing warm == cold latency because adapt_cached's
  split_at_page_breaks walks the source prefix per block (quadratic).

What was run:
- cargo run --release --example xref_measure -- 5 450 (exit=0; table in the doc)
- cargo run --release --example stages -- doc150.tex 2 / doc535.tex 2
- cargo test --test incremental --test cache_counters:
  test result: ok. 1 passed; 0 failed; 1 ignored (incremental, 375.30s)
  test result: ok. 1 passed; 0 failed (cache_counters)

Next step: Commander ruling on #575; the adapter prefix scans are a
separate render-pipeline performance finding for its owner.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
@d-q222

d-q222 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

daniel-parent independent review (subagent)
Verdict: APPROVE

  1. Code read confirms the counters are genuinely inert. RenderCache::stats() (crates/render-pipeline/src/incremental.rs) is byte-for-byte unchanged — still (*self.hits.borrow(), *self.misses.borrow()), reading only the original hits/misses RefCells, untouched by the new Cell fields. adapted() and assembled() preserve their exact prior return value (let hit = ...; bump(...); hit), so no caller's control flow changes. note_label_pass() is pub(crate), called only from render_cached's loop in crates/render-pipeline/src/lib.rs right after the existing passes += 1; and before the existing adapter::adapt_cached(...) call — the diff there is exactly those three added lines, nothing else in the hunk changed. counters()/CacheCounters/since() are new, additive, read-only, and their only readers are the new example and test. RenderCache still derives Default, so new()'s initialization is untouched.
  2. Display-list diff on 20 real fixtures, before (origin/main 36fe7ec3) vs after (PR head dbce9043): toc/{01,05,09,10,15,19,21,24,33}-*.tex, thebibliography/main.tex, floats/08-table-ref.tex, float-body/{01,02,18}-*.tex, display-placement/fixtures/{15-align-tag-notag-eqref,17-numberwithin-section,18-subequations}.tex, footnotes/01-single.tex, multicol/01-two-balanced.tex, math-symbols/03-binary.tex — biased toward cross-reference/label/toc/bibliography content since that's the code path touched. Each fixture was sent as two requests (revision 1 "cold", revision 2 identical text "warm") in one flashtex-render worker session (protocol::serve, one live RenderCache across both, matching the real IDE path) with display-list-v2 negotiated. All 20 fixtures accepted the capability and returned status: "ok" on both requests (confirming adapted()/assembled()/note_label_pass() were actually exercised on both the miss and hit paths, not skipped), and diff -q found 0 byte differences across all 20 × 4 output lines (~50 MB of base-checkout output compared).
  3. cargo test --release --test cache_counters on the PR-613 worktree: counters_track_lookups_and_label_passes ... ok.
  4. Bonus corroboration beyond the required scope: the pre-existing tests/incremental.rs::incremental_output_is_byte_identical_over_200_edits_of_a_27_page_document also passes on the PR-613 worktree (200 edits, byte-identical), independently reinforcing the no-behavior-change claim.

@GoKubar
GoKubar merged commit 751eb30 into main Sep 15, 2026
20 of 21 checks passed
@GoKubar
GoKubar deleted the agent/daniel-parent/xref-measure-step0 branch September 16, 2026 04:17
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