Skip to content

ADR 0094: What startup actually costs

Status

Accepted

Context

Startup had been measured only end-to-end — 81 ms for a small file, 203 ms for a 277 KB one — which is enough to know whether it is acceptable and nothing at all about where the time goes. This ADR records the breakdown, because the useful result of measuring is usually the shape rather than the total.

Decision

The measurement

Timestamps at each phase, offscreen, with a bare Qt6 widgets app built alongside as the floor:

phase 11 KB 277 KB
QApplication 1.7 ms 1.6 ms
read the file 0.3 ms 0.4 ms
MainWindow 19 ms 15 ms
addBuffer 19 ms 113 ms
show + event loop 0.7 ms 0.7 ms
total 43 ms 98 ms

A bare QMainWindow with a status bar reaches its event loop in 13.7 ms on this machine. That is the floor nothing can go below, and it means our own MainWindow costs about 7 ms — small enough not to be worth attacking.

Reading the file is 0.3 ms. The buffer is not the problem and never was.

What scales is the first parse

Inside addBuffer, for the 277 KB file:

loadConfig 1.0 ms
rebuildSyntax 3.0 ms
refreshCache 97 ms
timers, panels 0.2 ms

refreshCache's cost is the initial Tree-sitter parse. It is not incremental — there is no previous tree to be incremental from — and Tree-sitter has no way to parse only the visible range, so the whole file is parsed before anything is drawn. 97 ms for 277 KB is simply what that costs; the equivalent for a 60 KB file is around 20 ms, and for 11 KB under 4 ms.

Deferring it so the window appears first was considered and not done: it trades a slower startup for a visible flash of uncoloured text, and at the sizes people actually edit the parse is already invisible.

AboutPanel was built for every buffer

Each buffer gets its own set of floating panels (ADR 0022), built eagerly in addBuffer. Timed individually, all of them are free — FindBar 0.08 ms, FileBrowserPanel 0.08 ms, HelpPanel 0.19 ms, CompletionPopup 0.04 ms, HoverPanel 0.02 ms — except one:

AboutPanel: 8.05 ms. It decodes :/ase.png and smooth-scales it, in its constructor, for a panel reached by Alt+I and opened approximately never. That cost was paid on startup and on every Ctrl+N, every file opened, every new tab.

It is now built on first Alt+I. Every call site already null-checked the pointer, so nothing else changed, and openAbout() calls refreshTheme() itself — a lazily built panel is themed correctly without the config reload a pre-built one would have received.

The other panels stay eager. 0.02 ms is not worth a lifetime rule.

Consequences

Startup, median of five, after this and ADR 0093:

file before after
11 KB 81 ms 81 ms
60 KB 112 ms 101 ms
277 KB 203 ms 121 ms

Most of the large-file gain is ADR 0093 — startup no longer waits for the language server handshake. The AboutPanel saving is real but 8 ms, which this end-to-end method cannot resolve; it was measured directly instead.

The first Alt+I now pays 8 ms to build the panel. It opens with an animation that is longer than that, and it happens once per buffer.

What is left is mostly Qt's own 14 ms and, for large files, a parse that has to happen. Neither is a bug, and saying so is the point of writing the numbers down: the next person to wonder whether startup can be made faster can read this instead of re-deriving it.