orca/docs/performance-audit.md

82 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Orca Performance Audit
Audit date: 2026-04-13
Branch: `Jinwoo-H/performance-improvement`
---
## Tier 1 — High Impact, Low Complexity
| # | Area | Issue | File(s) | Status |
|---|------|-------|---------|--------|
| 1 | Renderer | `App.tsx` has 53 separate Zustand subscriptions — nearly every state change re-renders the root and cascades through the entire tree | `src/renderer/src/App.tsx:40-180` | DONE (53→22 subs, memo barriers on 4 children) |
| 2 | Renderer | `Terminal.tsx` has unscoped `useAppStore.subscribe()` — fires on every store mutation, does O(worktrees×tabs) scan each time | `src/renderer/src/components/Terminal.tsx:645-659` | DONE |
| 3 | Terminal | No IPC batching — every PTY data chunk is a separate `webContents.send` call, hundreds/sec under load | `src/main/ipc/pty.ts:173-177` | DONE |
| 4 | Terminal | Divider drag calls `fitAddon.fit()` on every `pointermove` pixel — xterm reflow can take 500ms+ with large scrollback | `src/renderer/src/lib/pane-manager/pane-divider.ts:90-119` | DONE |
| 5 | Main | Startup blocks: `openCodeHookService.start()` and `runtimeRpc.start()` awaited sequentially before window opens | `src/main/index.ts:131-168` | DONE |
| 6 | Main | `persistence.ts` uses `readFileSync`/`writeFileSync`/`renameSync` on the main thread — blocks during startup and every 300ms save | `src/main/persistence.ts:59-125` | DONE |
| 7 | Worktree | `worktrees:listAll` iterates repos sequentially with `await` — total time = sum of all repos instead of max | `src/main/ipc/worktrees.ts:53-84` | DONE |
| 8 | Browser | Reverse Map scan O(N) on every mouse/load/permission event in `BrowserManager` | `src/main/browser/browser-manager.ts:91-96` | DONE |
| 9 | Browser | `before-mouse-event` listener fires for ALL mouse events on ALL guests, even background ones | `src/main/browser/browser-guest-ui.ts:78-93` | DONE |
| 10 | Worktree | `refreshGitHubForWorktree` bypasses 5-min cache TTL on every worktree switch — fires GitHub API calls on rapid tab switching | `src/renderer/src/store/slices/worktrees.ts:467-489` | DONE |
---
## Tier 2 — Medium Impact
| # | Area | Issue | File(s) | Status |
|---|------|-------|---------|--------|
| 11 | Main | `git/repo.ts`: `execSync('gh api user ...')` and chains of 5 sync git processes block main thread | `src/main/git/repo.ts:87-138` | TODO |
| 12 | Main | `hooks.ts`: `readFileSync`/`writeFileSync`/`mkdirSync`/`gitExecFileSync` in IPC handlers | `src/main/hooks.ts:113-416` | TODO |
| 13 | Renderer | `useSettings` returns entire `GlobalSettings` (~30+ fields) — any setting change re-renders all consumers | `src/renderer/src/store/selectors.ts` | TODO |
| 14 | Renderer | Tab title changes bump `sortEpoch` for ALL worktrees → triggers WorktreeList re-sort on every PTY title event | `src/renderer/src/store/slices/terminals.ts:325` | TODO |
| 15 | Renderer | `CacheTimer`: one `setInterval` per mounted card (20 cards = 20 intervals/sec), each with O(n) selector work | `src/renderer/src/components/sidebar/CacheTimer.tsx:28-48` | TODO |
| 16 | Renderer | Three simultaneous 3-second polling intervals per active worktree (git status, worktrees, stale conflict) | `src/renderer/src/components/right-sidebar/useGitStatusPolling.ts` | TODO |
| 17 | Renderer | 6 components missing `React.memo`: `SourceControl`, `RightSidebar`, `EditorPanel`, `ChecksPanel`, `FileExplorer`, `TabBar` | Various files in `src/renderer/src/components/` | DONE (8 components wrapped) |
| 18 | Worktree | Git polling fires immediately on every worktree switch (burst of `git status` + `git worktree list`) | `src/renderer/src/components/right-sidebar/useGitStatusPolling.ts:69-103` | REVERTED (150ms debounce caused visible flash on switch) |
| 19 | Worktree | `FileExplorer` `dirCache` discarded on every worktree switch — re-fetches entire tree from scratch | `src/renderer/src/components/right-sidebar/useFileExplorerTree.ts:27,81-96` | TODO |
| 20 | Terminal | `pty:resize` uses `ipcRenderer.invoke` (round-trip) instead of fire-and-forget `send` | `src/preload/index.ts:203-205` | DONE |
| 21 | Terminal | Flow control watermarks defined but never enforced for local PTYs — unbounded output floods renderer | `src/main/providers/local-pty-provider.ts:330-332` | TODO |
| 22 | Browser | `BrowserPane` subscribes to entire `browserPagesByWorkspace` map — any tab's navigation re-renders all panes | `src/renderer/src/components/browser-pane/BrowserPane.tsx:312` | TODO |
| 23 | Browser | `findPage`/`findWorkspace` do O(N) `Object.values().flat().find()` scans on every navigation event | `src/renderer/src/store/slices/browser.ts:221-240` | TODO |
| 24 | Browser | Download progress IPC fires at full Chromium frequency (many/sec) | `src/main/browser/browser-manager.ts:421-430` | TODO |
| 25 | Main | `detectConflictOperation` runs 4 `existsSync` + `readFile` on every 3s poll before git status | `src/main/git/status.ts:236-265` | DONE |
| 26 | Main | `getBranchCompare`: `loadBranchChanges` and `countAheadCommits` run sequentially | `src/main/git/status.ts:374-375` | DONE |
| 27 | Main | `addWorktree` calls 4-5 synchronous git processes from IPC handler | `src/main/git/worktree.ts:116-157` | DONE |
---
## Tier 3 — Lower Impact / Architectural
| # | Area | Issue | File(s) | Status |
|---|------|-------|---------|--------|
| 28 | Terminal | SSH relay `FrameDecoder` uses `Buffer.concat` on every chunk (quadratic copy) | `src/relay/protocol.ts:84` | TODO |
| 29 | Terminal | SSH relay replay buffer uses string concatenation (quadratic allocation) | `src/relay/pty-handler.ts:68-72` | TODO |
| 30 | Terminal | `extractLastOscTitle` regex runs on every PTY chunk with no fast-path bail | `src/shared/agent-detection.ts:23-39` | TODO |
| 31 | Terminal | Binary search calls `serialize()` up to 16× at shutdown | `src/renderer/src/components/terminal-pane/TerminalPane.tsx:579-596` | TODO |
| 32 | Browser | `capturePage()` captures full viewport then crops — should pass rect directly | `src/main/browser/browser-grab-screenshot.ts:36` | TODO |
| 33 | Browser | Parked webviews retain full 100vw×100vh compositor surfaces | `src/renderer/src/components/browser-pane/BrowserPane.tsx:94-107` | TODO |
| 34 | Browser | `onBeforeSendHeaders` intercepts every HTTPS request even when UA override unused | `src/main/browser/browser-session-registry.ts:126-137` | TODO |
| 35 | Main | `setBackgroundThrottling(false)` wastes CPU when window is minimized | `src/main/window/createMainWindow.ts:97` | TODO |
| 36 | Main | `warmSystemFontFamilies()` competes with startup I/O | `src/main/system-fonts.ts:30-32` | TODO |
| 37 | Renderer | Session persistence effect has 15 deps — fires on every tab title change | `src/renderer/src/App.tsx:239-283` | TODO |
| 38 | Renderer | Per-card `fetchPRForBranch` on mount — 30 worktrees = 30 simultaneous IPC calls | `src/renderer/src/components/sidebar/WorktreeCard.tsx:128-132` | TODO |
| 39 | Renderer | Synchronous full `monaco-editor` import blocks EditorPanel chunk evaluation | `src/renderer/src/components/editor/EditorPanel.tsx:7` | TODO |
| 40 | Worktree | `removeWorktree` runs `git worktree list` twice (pre and post removal) | `src/main/git/worktree.ts:163-208` | TODO |
| 41 | Worktree | `worktrees:list` can trigger duplicate `git worktree list` when cache is dirty | `src/main/ipc/worktrees.ts:86-116` | TODO |
| 42 | Renderer | SSH targets initialized sequentially in a `for...await` loop | `src/renderer/src/hooks/useIpcEvents.ts:232-252` | TODO |
---
## Key Themes
1. **Zustand subscription granularity**`App.tsx` subscribes to 53 slices, `Terminal.tsx` subscribes to everything, `useSettings` returns the full object. Almost any state change cascades through the entire tree.
2. **Synchronous I/O on the main thread**`persistence.ts`, `hooks.ts`, `git/repo.ts`, and `git/worktree.ts` use `readFileSync`/`writeFileSync`/`execSync` in startup and IPC handler paths.
3. **Unthrottled high-frequency events** — PTY data (no IPC batching), divider drag (fit on every pixel), download progress (no throttle), `before-mouse-event` (all mouse events on all guests), CacheTimer (20 intervals/sec).
4. **Sequential operations that could be parallel** — Startup server binds, repo iteration in `listAll`, git ref probing, `loadBranchChanges`/`countAheadCommits`, SSH target init.
5. **Aggressive polling** — Three 3-second intervals per worktree, per-card 1-second cache timers, per-card 5-minute issue polling, 250ms error-page detection.