Skip to content

perf: send an opened document across IPC once - #320

Merged
lidonggui merged 1 commit into
mainfrom
perf/open-reply-once
Oct 11, 2026
Merged

lidonggui merged 1 commit into
mainfrom
perf/open-reply-once

Conversation

@lidonggui

Copy link
Copy Markdown
Collaborator

What

The openPath, openDialog and activateTab replies no longer carry the document. They name the note ({ path, documentId }), and the document reaches the renderer once, on documentOpened. Every open and activation already publishes that event before it replies.

Why

A packaged renderer CPU profile of opening the 8 MB bench document showed the preload spending ~490 ms in isNotoDocumentWire, almost all of it in its plain-JS SHA-256 of the text. That check ran twice per open: once for the documentOpened event (which App.tsx adopts) and once for the invoke reply, which echoed the same FileTruthOpenReplyV1 but was only read for ok. Main also serialised the ~24 MB payload twice.

  • WorkspaceOpenReplyV1.opened is now WorkspaceOpenedNoteV1 | null. A null still means a dismissed dialog or a tab that is not open.
  • session.openPath returns an internal WorkspaceSessionOpenV1 (main keeps the full reply per tab). The narrowing happens at the IPC boundary in open-reply.ts.
  • The reply validator refuses a reply that carries the document again, so an echo can't creep back in.

Measured

Packaged app, Linux x64 under Xvfb, BENCH_RUNS=5 BENCH_IDLE_LAG=0, same box, before (main 33db02c) and after:

doc open before open after
large 2 MB 797 ms 674 ms
huge 8 MB 2,226 ms 1,694 ms

The Linux baseline matches the macOS CI run on e6d373c (huge 2,137 ms). Profiled openPath to first paint: 2,264 → ~1,645 ms.

Verification

  • pnpm typecheck and pnpm test pass (2,231 tests), including the new workspace-open-reply.test.ts.
  • Packaged e2e on Linux: tabs, shell (open from the empty state, recent list, outline), quick-tabs, reopen, session-restore, code-viewer, links, trail and external-change. 42/42 pass.

Next residual (huge, from the same profiles)

main parseDocument ~320 ms (about half is building stand-in mdast that engine-owned blocks never read), the one remaining preload hash ~200 ms, docFromSpans ~140 ms, EditorState.create ~110 ms (typoraMarks fullScan ~50 ms), and createEnrichFlags re-running canSkipDialectEnrich ~50 ms.

The openPath, openDialog and activateTab replies echoed the full
FileTruthOpenReplyV1 that main had just published on documentOpened.
The renderer adopts the event and reads only ok from the reply, yet the
8 MB note was serialised twice and the preload re-hashed both copies in
plain-JavaScript SHA-256 (~250 ms each on the huge bench document).

The replies now name the note ({ path, documentId }); the document
travels once, on the event every open and activation already publishes
before replying. The reply validator refuses a reply that carries the
document again.

Packaged Linux bench, 5 runs: huge open 2226 -> 1694 ms, large
797 -> 674 ms.
@lidonggui
lidonggui merged commit f25d1fe into main Oct 11, 2026
7 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.

1 participant