feat(history-import): benchmark ConversationView open/scroll/stream against the Mirror baseline (phase 1b-C) - #365
Open
zxch3n wants to merge 2 commits into
Conversation
zxch3n
force-pushed
the
feat/conversation-view-benchmarks
branch
from
September 4, 2026 00:02
e50d078 to
e36b775
Compare
…gainst the Mirror baseline (phase 1b-C) `bench:open` now defaults to the deterministic synthetic replay at `--scale=1,10` (`--fixture` keeps taking a desensitized capture) and measures the view next to the full-Mirror baseline: `view.open` (import + view + tail hydrate -> one renderable row per turn), `view.readAll` (the `doc.history` bridge's first read), `view.scroll` (a 30-turn `ensureRange` window advancing 20 times, p99), `view.stream` (100 text deltas into the tail turn with the view attached, p99) and `view.append`. View tasks run before the baseline so the Mirror's garbage is not charged to them, and the summary prints the phase 1b acceptance checks (open <= 50 ms at x10, stream p99 <= 4 ms). Two view fixes the benchmark surfaced: - `rebuildIndex` / `evict` resolved positions with `ids.indexOf` per turn, O(n^2) per structural change (an append at 2,400 turns took seconds); they use a position map now. - `itemCount` is read for assistant turns only and `planCount` only when a plan exists, which trims two of the five wasm calls per turn at open. Model: claude-fable-5-1
…rashing the bench summary The full-schema Mirror throws `unreachable` on the desensitized real fixture at x10 (570 turns, 56k items); the reporter now prints the error per task and in the summary. Model: claude-fable-5-1
zxch3n
force-pushed
the
feat/conversation-view-benchmarks
branch
from
September 4, 2026 00:03
e36b775 to
be3c121
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1b-C: benchmarks that pin the before/after of the ConversationView work (stacked on #364), plus two view fixes the benchmark surfaced.
packages/history-import/benchmarks/open-conversation.bench.tsnow defaults to the deterministic synthetic replay (--turns, default 120 user turns → 240 turns) at--scale=1,10, keeps--fixture=<file>for a desensitizedbench:capture(never committed), and adds view tasks next to the Mirror baseline:view.open(import + view + tail hydrate → one renderable row per turn),view.readAll(what a reader still on thedoc.historybridge pays on first access),view.scroll(a 30-turnensureRangewindow advancing 20 times, p99),view.stream(100 text deltas into the tail turn with the view attached, p99) andview.append. View tasks run before the baseline so the Mirror's garbage is not charged to them; the summary prints the acceptance checks.rebuildIndex/evictresolved positions withids.indexOfper turn (O(n²) per structural change; an append at 2,400 turns took seconds), now a position map.itemCountis read for assistant turns only andplanCountonly when a plan exists, which trims two of the five wasm calls per turn at open.Numbers (M-series laptop,
bench:open, mean unless noted; before = full-schemaMirroropen, after =view.open)unreachable(wasm) while materializing;doc.toJSON()alone is 5.2 sLoroDoc.importof the snapshot; no baseline to compare)Acceptance (from the phase 1b task)
open≤ 50 ms at ×10 (~2,400 turns): met on the synthetic ×10 fixture (46.2 ms). On the desensitized real fixture the turns average ~100 items each andview.openis 90 ms at ×1 and 199 ms at ×10 (117 ms of which is decoding the 48.5 MiB snapshot): the cost is the eager hydration of the 20-turn tail (toJSONof ~2,000 items), not the index. Not adjusting the criterion; the item-heavy shape is reported as is.streamp99 ≤ 4 ms: met on every fixture (0.08–0.22 ms).view.scroll(30-turn window) p99 is 4–17 ms on the synthetic fixtures and 148–406 ms on the item-heavy real fixture (~100 items per turn; a window is ~3,000 items oftoJSON). The renderer hydrates around the viewport with a 2-screen prefetch, so a scroll normally hits already-hydrated turns; a cold far jump on such a session pays this once.RuntimeError: unreachableat 4 GiB loro-dev/loro#1092: container-by-container reads (LoroMap.get/LoroList.get/LoroText.toJSON, which is how loro-mirror builds its initial state) retain ~4 KB of wasm memory per container and scale superlinearly, so a doc of ~1M containers, or a few docs of ~330k containers alive at once, exhausts the 4 GiB wasm32 limit and traps with a bareRuntimeError: unreachable.doc.toJSON()on the same docs needs tens to hundreds of MiB. The standalone repro (synthetic doc, no fixture data) is in the issue.🤖 Generated with Claude Code