Skip to content

Commit 4fdf4ff

Browse files
Apply PR #40427: some experimental perf improvements
2 parents 61cb2e3 + 27ecc46 commit 4fdf4ff

35 files changed

Lines changed: 1534 additions & 309 deletions
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Real desktop renderer profile
2+
3+
## Scope
4+
5+
- Revision: `60e23fee161982d572fafbf6cc0df63ac8ac87cc`
6+
- Runtime: production Electron preview from the current `dev` build
7+
- Data: the live `opencode.db` used by Beta, queried read-only for targets active in the preceding 24 hours
8+
- Contents are never written to reports. Reports contain rank labels, counts, sizes, durations, and source locations only.
9+
- The database is shared by channels, so rows cannot be attributed exclusively to the Beta executable.
10+
11+
The final attributed run covered 26 active sessions: 7 roots and 19 child/subagent sessions. The selected root-session targets were:
12+
13+
| Rank | Serialized bytes in window | Messages | Parts | User turns |
14+
| --- | ---: | ---: | ---: | ---: |
15+
| p50 | 13,211 | 6 | 14 | 3 |
16+
| p95/max | 7,665,253 | 421 | 1,883 | 51 |
17+
18+
The p95 and maximum were the same root session. The harness intentionally runs it twice, so those rows are warm/cold repetitions rather than independent samples.
19+
20+
## Confirmed main-thread work
21+
22+
### 1. Response parsing and synchronous continuation
23+
24+
The largest consistently attributed script entry starts at `Response.text.then` in `packages/sdk/js/src/v2/gen/client/client.gen.ts:171-172`. The client reads the complete body as text and calls synchronous `JSON.parse` on the renderer. The LoAF duration also includes the promise microtasks that synchronously ingest the parsed result and update the UI.
25+
26+
- Large-session navigation: 139-391 ms attributed to this entry point, including 38-88 ms forced style/layout.
27+
- History pagination: 388 ms attributed, including 64 ms forced style/layout.
28+
- Home: two response continuations took 77 ms and 61 ms, including 22 ms forced style/layout.
29+
30+
This is the first boundary to split or move off-thread. The measurement does not imply that all attributed time is `JSON.parse`; it includes downstream work in the same promise checkpoint.
31+
32+
### 2. Timeline measurement and scroll reconciliation
33+
34+
History loading was the heaviest repeatable workflow.
35+
36+
| Metric | Clean-run range |
37+
| --- | ---: |
38+
| Long tasks | 6-9 |
39+
| Total long-task time | 763-1,097 ms |
40+
| Worst long task | 138-309 ms |
41+
| Workflow time | 4.7-5.3 s |
42+
43+
The attributed run found:
44+
45+
- TanStack Virtual `ResizeObserver`: 173 ms, including 42 ms forced layout.
46+
- TanStack Virtual scroll handler: 160 ms.
47+
- `ScrollView` scroll handler at `packages/ui/src/components/scroll-view.tsx:173-202`: 65 ms, including 64 ms forced layout.
48+
- The timeline's virtualizer and resize anchoring are configured at `packages/app/src/pages/session/timeline/message-timeline.tsx:413-486`.
49+
50+
The next optimization target is the response-to-projection-to-measurement pipeline, especially reducing synchronous DOM reads and repeated resize/scroll reactions while prepending history.
51+
52+
### 3. Renderer-side Shiki WASM startup
53+
54+
A small-session navigation loaded `@shikijs/engine-oniguruma` in the renderer and spent 58-62 ms evaluating its WASM module. Session Markdown parsing/highlighting and Pierre highlighting have workers, but renderer-side Shiki/Pierre initialization is still reachable. The bundled path is created by `getSharedHighlighter(... preferredHighlighter: "shiki-wasm")` and should be audited separately from worker execution.
55+
56+
### 4. Home startup and geometry
57+
58+
Across clean runs, Home produced 3-4 long tasks, 277-394 ms total, with a 127-172 ms maximum.
59+
60+
Attribution showed:
61+
62+
- Main module evaluation: 137 ms.
63+
- Two response continuations: 77 ms and 61 ms.
64+
- 22 ms forced style/layout.
65+
- CPU samples in `ScrollView.updateThumb` and `home-scroll-controller.ts:56-69` (`getComputedStyle`, every header's `offsetTop`, and `scrollTop`).
66+
67+
### 5. Review opening
68+
69+
The current real workspace review was not a major hotspot. Clean runs ranged from zero to two long tasks, with a 0-106 ms maximum. The attributed run had a 35 ms delegated click handler and 11 ms forced style/layout. This corpus had one diff viewer and does not validate large-review behavior.
70+
71+
## Markdown renderer conclusion
72+
73+
No current real text part exceeded 19,466 characters in the initial corpus audit. In the desktop traces:
74+
75+
- Marked, KaTeX, and Shiki parsing did not appear as renderer CPU hotspots because they run in the Markdown worker.
76+
- Markdown worker response handling appeared as a 43 ms entry in one run.
77+
- Renderer `postMessage`, `innerHTML`, HTML parsing, sanitization-related DOM parsing, and token DOM updates appeared in low-single-digit samples and accumulated tens of milliseconds, but none was independently responsible for the observed long tasks.
78+
- Checksumming did not appear above the 1 ms CPU reporting threshold.
79+
80+
For this real 24-hour corpus, Markdown's remaining renderer stages are secondary to response ingestion and timeline layout. This does not establish safety for an exceptional multi-megabyte text part.
81+
82+
## Reviewed but not measured
83+
84+
The source audit also found scale-sensitive renderer paths that this real workflow did not exercise enough to assign runtime numbers:
85+
86+
- Terminal buffer serialization/restoration over up to 10,000 rows.
87+
- In-file search text-node scans, DOM `Range` creation, and match geometry.
88+
- Large file/diff preprocessing and virtualized file DOM work.
89+
- Command-palette filtering and unvirtualized result rendering.
90+
- Draft recursive serialization and blob hashing.
91+
- Generic persisted-state parse/merge/stringify.
92+
- Large review trees and large diff switching.
93+
94+
No synthetic data was introduced merely to force these paths. They remain candidates for a future profile when corresponding real data exists.
95+
96+
## Reproduce
97+
98+
From `packages/app`:
99+
100+
```powershell
101+
$env:OPENCODE_PROFILE_DIAGNOSTICS = "0"
102+
$env:OPENCODE_PROFILE_OUTPUT = "C:\tmp\opencode\markdown-profile-results\run"
103+
bun run e2e/performance/real-desktop-profile.ts
104+
```
105+
106+
The script builds and launches Electron, points it at the shared real database, enables Electron 42's `AlwaysLogLOAFURL` feature for custom-protocol script attribution, profiles the workflows, and terminates the complete process tree in `finally`.
107+
108+
For Chrome traces and a 1 ms CPU sampler:
109+
110+
```powershell
111+
$env:OPENCODE_PROFILE_DIAGNOSTICS = "1"
112+
$env:OPENCODE_PROFILE_OUTPUT = "C:\tmp\opencode\markdown-profile-results\diagnostic"
113+
bun run e2e/performance/real-desktop-profile.ts
114+
```
115+
116+
Diagnostic timing is perturbed by tracing and sampling. Use clean runs for blocking-time numbers and diagnostic runs only for attribution.
117+
118+
Useful outputs from this audit:
119+
120+
- `C:\tmp\opencode\markdown-profile-results\loaf-positions\renderer-profile.json`
121+
- `C:\tmp\opencode\markdown-profile-results\diagnostic\renderer-profile.json`
122+
- `C:\tmp\opencode\markdown-profile-results\diagnostic\traces`
123+
124+
## LoAF attribution
125+
126+
Electron custom protocols normally produce an empty `PerformanceLongAnimationFrameTiming.scripts` array. Electron 42 supports custom-protocol attribution behind `--enable-features=AlwaysLogLOAFURL`; the harness enables it through an environment-guarded `app.commandLine.appendSwitch` and verifies it with an 80 ms calibration callback.
127+
128+
References:
129+
130+
- https://developer.chrome.com/docs/web-platform/long-animation-frames
131+
- https://www.electronjs.org/docs/latest/api/command-line-switches#chromium-features-relevant-to-electron-apps
132+
- https://github.com/electron/electron/pull/49706

0 commit comments

Comments
 (0)