How these were measured
- Timings come from the same source and the same release profile as the
binary you download, with one flag different: the measuring harness is a
non-default cargo feature, so the download carries none of it and the measured build is
cargo build --release --features bench. Same optimisations, same code paths, one passenger the shipped artifact does not have to carry. The size figures under Footprint are the exception, and are measured on a build without that flag — a published size that quietly included the measuring apparatus would not be a size of this app. - Every figure is repeated. The bars are medians; the small print under each is the range across runs. One sample from a laptop is not a measurement.
- Each run gets a fresh copy of the vault and a fresh process, so nothing
inherits a remembered scroll position or a previous run's index. It does inherit
the file cache — the vault is staged moments before the app is pointed at it — so the
launch figures are the best case, and are labelled that way. The first
launch after the Mac is turned on is measured separately, with the cache emptied by
purge(8), and it is the slower of the two. - Sometimes we test under heavy load from other applications, and every set of figures below says which. Load average is how many things wanted a CPU at once, averaged over a minute; divided by this machine's 8 cores it is a number you can read directly. 1.0 per core or more is a heavy load — every core has work queued behind yours: a compile, a backup, a photo library being indexed. 0.35 per core or less is a light load — a browser and a chat app open, nothing grinding. In between is busy, and it is where a real Mac spends most of its day: this one idles near 0.5 per core with no user apps open at all, because macOS is always doing something of its own.
- We used to publish only figures taken on a machine doing nothing else. That state barely exists, so the page went stale while better numbers piled up unpublished. Now the conditions travel with the figure instead — a number measured on a busy machine is worth more than a number from six months ago, as long as it says so.
- The whole suite is one checked-in script,
script/bench.py, and the corpora are public-domain books from Project Gutenberg. You can reproduce all of it.
These are numbers from one machine on one day, not a promise about yours. The hardware and conditions are right below — including how much memory was free and how busy the machine was, because a machine under pressure produces slower and noisier results and you deserve to know which you are reading before you read the numbers.
Machine and conditions
- Processor
- Apple M2 (Mac14,15)
- Machine
- 8 cores, 8 GB
- OS
- Darwin 26.1
- Toolchain
- rustc 1.97.0 (2d8144b78 2026-07-07)
- Memory free
- 46% at start
- Load average
- 4.84 at start, across 8 cores — the load when the run began. The timed runs start only after the suite's own build has cleared, and the chips under each heading below say the load while those particular figures were taken.
- Commit
0a6394c- Run
- 2026-09-02T21:41:04-0700
Build
A from-scratch release build — cargo clean --release first, so every dependency and the fat-LTO link are inside the number. The warm figure is what you wait for after changing one line of app code; it still relinks the whole binary, which is the price of lto = "fat" and codegen-units = 1.
- ProfileThe cargo release profile every timed build uses, and the one the download is built with: lto = "fat" (link-time optimisation across the whole program), codegen-units = 1 (each crate compiled as one unit: slowest to build, fastest to run) and strip = true (debug symbols removed from the binary).
- release: lto=fat, codegen-units=1, strip=true
Tests
How long the two test suites take to run — each as the command a developer types, timed when nothing needs compiling, so the figure is the suite itself and grows as tests are added. They are two numbers on purpose: cargo test --workspace is the app's correctness suite; the pixel suite is behind its own cargo feature and is the one that checks how anything looks.
- Tests in the Rust suiteHow many tests the timed invocation ran, summed over cargo's own "test result" lines (one per test binary); ignored tests are not counted.
- 1,333
- Tests in the pixel suiteHow many tests the timed invocation ran, summed over cargo's own "test result" lines (one per test binary); ignored tests are not counted.
- 21
Launch — best case
From exec to the first settled frame — the whole wait, including the dynamic linking that happens before qm runs a line of its own code. Each run gets a fresh copy of the vault and a fresh process, so nothing inherits a remembered scroll position or a previous run's index. These are best-case figures: an idle machine, and a file cache still holding the vault that was staged a moment earlier. That is the second launch, not the first — the first is measured separately below. Backlinks are indexed in the background and the window does not wait for them, so each row says when that landed rather than counting it as a step.
- busy load (0.49 per core)
- warm file cache
- 3 runs
Empty vault
- Process startTime between the harness spawning the process and the first statement of main(): exec, dyld loading and binding the dynamic libraries the binary links, and the Rust runtime's own start. Computed as the app's start timestamp (taken at the top of main) minus the harness's timestamp from just before it spawned the process. Unlike every phase below, it is not a gap between two timestamps the app takes itself — the app cannot time what happens before it runs — which is why the harness supplies one end of it. exec and dynamic linking, before qm's own code runs 16 ms
- Platform readyFrom the first statement of main() to the platform_ready mark, which the app stamps as the first thing inside gpui's run callback: everything gpui does to bring up NSApplication, the Metal renderer and the text system before any of qm's own code runs. The floor under any launch, separated out so qm's share of startup is not confused with it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. gpui brings up NSApplication, Metal and the text system 80 ms
- UI readyFrom platform_ready to the ui_ready mark: gpui-component's initialisation, the theme, registering every key binding and building the application menu. Nothing has been read from the vault yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. theme, key bindings and the menu bar 19 ms
- Scan vaultFrom ui_ready to the vault_scanned mark, stamped when the vault folder has been walked and every .md file indexed. Three things happen inside it, in order. First, the window's saved geometry is read. Second, gpui creates the macOS window and its Metal renderer — open_window makes the window before it builds the view inside it, and it is while building that view that the vault is scanned. Third, the scan itself, which reads directory entries only (file names and folders, skipping hidden ones), never the contents of a note; note text is read later, by opening a note and by the backlink index in the background. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the macOS window is created, then the tree is walked and every .md file indexed 64 ms
- Open first noteFrom vault_scanned to the first_file_open mark, stamped once the workspace has read the note it opens at launch (the last note open in this vault, or the note a new vault is given), parsed it into blocks, and placed the caret where it last was. Reading the file and building the markdown block map are both inside it; so is recording the vault in the known-vaults list; the background read for the backlink index is started here but not waited for; nothing has been painted yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. read the file, parse it into blocks 49 ms
- Window readyFrom first_file_open to the window_opened mark, stamped when gpui's open_window returns. Inside it: the rest of building the view tree once the first note is ready (the root view that wraps the shell, the close-button handler, the benchmark's own frame callbacks), and then the first layout and paint of the note. gpui draws a window once — into a scene that is not yet presented — before open_window returns, so this phase holds the first time every visible block is laid out and the first glyphs are rasterised. That first layout is against the raw window size; the content column's real width is only known once this draw has measured its scroll viewport, which is what the next drawn frame uses. The macOS window itself was created earlier, inside the Scan vault phase. Nothing is on screen yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the view tree is finished and the note is laid out and painted once, not yet on screen 34 ms
- First display tickFrom window_opened to the first_frame mark. That mark is a callback registered before any frame, and gpui runs such callbacks at the start of the next display-link tick, before that tick's draw — so this phase is the wait between open_window returning and the display's first tick for this window: the window becoming visible and the display link starting. No layout happens inside it; the first frame that reaches the screen is drawn immediately after it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the window is visible and the display link has started; no layout happens here 11 ms
- First frame on screenFrom the first_frame mark to the settled_frame mark, which is registered inside the first_frame callback and therefore fires at the start of the display-link tick after it. Inside: the draw of the first frame that reaches the screen, laid out against the scroll viewport the pre-open draw measured — the content column's real width, which that draw, laid out against the raw window size, did not yet know; this is why the page calls this the settled frame — then its presentation, and the wait for the following tick. By this mark the first settled frame is on screen; the figure includes up to one display refresh of waiting for the tick that stamps it. This mark plus the process-start phase is the headline bar above. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. drawn against the real viewport and presented; marked at the next tick 68 ms
- Notes in vaultHow many .md files the vault scan found in the staged copy of the vault, as the app reported it once the window was up. A new empty vault shows more than zero because qm writes its starter notes into it.
- 2
- Blocks in open noteHow many blocks — paragraphs, headings, list items, code fences, blank-line runs — the note that was opened at launch parsed into: what the first frame's layout had to consider.
- 17
- Open note sizeThe size in bytes of the opened note's text as the app holds it in memory.
- 1.6 kB
- Backlinks indexedWhen the backlink index landed: the backlinks_built mark, stamped on the main thread when the background read of every note's link sources finished and its result was installed, in milliseconds from the first statement of main(). It is a moment, not a phase: since 2026-08-17 the window does not wait for it, so it can land after the first frames, and "had not landed when the run ended" means the harness quit the app (which it does shortly after the second frame) before the mark was stamped. Earlier figures, taken when the index was built on the launch path, are not comparable with these.
- 321 ms after start, in the background
Moby Dick
- Process startTime between the harness spawning the process and the first statement of main(): exec, dyld loading and binding the dynamic libraries the binary links, and the Rust runtime's own start. Computed as the app's start timestamp (taken at the top of main) minus the harness's timestamp from just before it spawned the process. Unlike every phase below, it is not a gap between two timestamps the app takes itself — the app cannot time what happens before it runs — which is why the harness supplies one end of it. exec and dynamic linking, before qm's own code runs 13 ms
- Platform readyFrom the first statement of main() to the platform_ready mark, which the app stamps as the first thing inside gpui's run callback: everything gpui does to bring up NSApplication, the Metal renderer and the text system before any of qm's own code runs. The floor under any launch, separated out so qm's share of startup is not confused with it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. gpui brings up NSApplication, Metal and the text system 68 ms
- UI readyFrom platform_ready to the ui_ready mark: gpui-component's initialisation, the theme, registering every key binding and building the application menu. Nothing has been read from the vault yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. theme, key bindings and the menu bar 18 ms
- Scan vaultFrom ui_ready to the vault_scanned mark, stamped when the vault folder has been walked and every .md file indexed. Three things happen inside it, in order. First, the window's saved geometry is read. Second, gpui creates the macOS window and its Metal renderer — open_window makes the window before it builds the view inside it, and it is while building that view that the vault is scanned. Third, the scan itself, which reads directory entries only (file names and folders, skipping hidden ones), never the contents of a note; note text is read later, by opening a note and by the backlink index in the background. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the macOS window is created, then the tree is walked and every .md file indexed 73 ms
- Open first noteFrom vault_scanned to the first_file_open mark, stamped once the workspace has read the note it opens at launch (the last note open in this vault, or the note a new vault is given), parsed it into blocks, and placed the caret where it last was. Reading the file and building the markdown block map are both inside it; so is recording the vault in the known-vaults list; the background read for the backlink index is started here but not waited for; nothing has been painted yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. read the file, parse it into blocks 31 ms
- Window readyFrom first_file_open to the window_opened mark, stamped when gpui's open_window returns. Inside it: the rest of building the view tree once the first note is ready (the root view that wraps the shell, the close-button handler, the benchmark's own frame callbacks), and then the first layout and paint of the note. gpui draws a window once — into a scene that is not yet presented — before open_window returns, so this phase holds the first time every visible block is laid out and the first glyphs are rasterised. That first layout is against the raw window size; the content column's real width is only known once this draw has measured its scroll viewport, which is what the next drawn frame uses. The macOS window itself was created earlier, inside the Scan vault phase. Nothing is on screen yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the view tree is finished and the note is laid out and painted once, not yet on screen 52 ms
- First display tickFrom window_opened to the first_frame mark. That mark is a callback registered before any frame, and gpui runs such callbacks at the start of the next display-link tick, before that tick's draw — so this phase is the wait between open_window returning and the display's first tick for this window: the window becoming visible and the display link starting. No layout happens inside it; the first frame that reaches the screen is drawn immediately after it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the window is visible and the display link has started; no layout happens here 10 ms
- First frame on screenFrom the first_frame mark to the settled_frame mark, which is registered inside the first_frame callback and therefore fires at the start of the display-link tick after it. Inside: the draw of the first frame that reaches the screen, laid out against the scroll viewport the pre-open draw measured — the content column's real width, which that draw, laid out against the raw window size, did not yet know; this is why the page calls this the settled frame — then its presentation, and the wait for the following tick. By this mark the first settled frame is on screen; the figure includes up to one display refresh of waiting for the tick that stamps it. This mark plus the process-start phase is the headline bar above. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. drawn against the real viewport and presented; marked at the next tick 70 ms
- Notes in vaultHow many .md files the vault scan found in the staged copy of the vault, as the app reported it once the window was up. A new empty vault shows more than zero because qm writes its starter notes into it.
- 1
- Blocks in open noteHow many blocks — paragraphs, headings, list items, code fences, blank-line runs — the note that was opened at launch parsed into: what the first frame's layout had to consider.
- 2,651
- Open note sizeThe size in bytes of the opened note's text as the app holds it in memory.
- 1.2 MB
- Backlinks indexedWhen the backlink index landed: the backlinks_built mark, stamped on the main thread when the background read of every note's link sources finished and its result was installed, in milliseconds from the first statement of main(). It is a moment, not a phase: since 2026-08-17 the window does not wait for it, so it can land after the first frames, and "had not landed when the run ended" means the harness quit the app (which it does shortly after the second frame) before the mark was stamped. Earlier figures, taken when the index was built on the launch path, are not comparable with these.
- 317 ms after start, in the background
Large vault
- Process startTime between the harness spawning the process and the first statement of main(): exec, dyld loading and binding the dynamic libraries the binary links, and the Rust runtime's own start. Computed as the app's start timestamp (taken at the top of main) minus the harness's timestamp from just before it spawned the process. Unlike every phase below, it is not a gap between two timestamps the app takes itself — the app cannot time what happens before it runs — which is why the harness supplies one end of it. exec and dynamic linking, before qm's own code runs 14 ms
- Platform readyFrom the first statement of main() to the platform_ready mark, which the app stamps as the first thing inside gpui's run callback: everything gpui does to bring up NSApplication, the Metal renderer and the text system before any of qm's own code runs. The floor under any launch, separated out so qm's share of startup is not confused with it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. gpui brings up NSApplication, Metal and the text system 74 ms
- UI readyFrom platform_ready to the ui_ready mark: gpui-component's initialisation, the theme, registering every key binding and building the application menu. Nothing has been read from the vault yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. theme, key bindings and the menu bar 17 ms
- Scan vaultFrom ui_ready to the vault_scanned mark, stamped when the vault folder has been walked and every .md file indexed. Three things happen inside it, in order. First, the window's saved geometry is read. Second, gpui creates the macOS window and its Metal renderer — open_window makes the window before it builds the view inside it, and it is while building that view that the vault is scanned. Third, the scan itself, which reads directory entries only (file names and folders, skipping hidden ones), never the contents of a note; note text is read later, by opening a note and by the backlink index in the background. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the macOS window is created, then the tree is walked and every .md file indexed 85 ms
- Open first noteFrom vault_scanned to the first_file_open mark, stamped once the workspace has read the note it opens at launch (the last note open in this vault, or the note a new vault is given), parsed it into blocks, and placed the caret where it last was. Reading the file and building the markdown block map are both inside it; so is recording the vault in the known-vaults list; the background read for the backlink index is started here but not waited for; nothing has been painted yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. read the file, parse it into blocks 31 ms
- Window readyFrom first_file_open to the window_opened mark, stamped when gpui's open_window returns. Inside it: the rest of building the view tree once the first note is ready (the root view that wraps the shell, the close-button handler, the benchmark's own frame callbacks), and then the first layout and paint of the note. gpui draws a window once — into a scene that is not yet presented — before open_window returns, so this phase holds the first time every visible block is laid out and the first glyphs are rasterised. That first layout is against the raw window size; the content column's real width is only known once this draw has measured its scroll viewport, which is what the next drawn frame uses. The macOS window itself was created earlier, inside the Scan vault phase. Nothing is on screen yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the view tree is finished and the note is laid out and painted once, not yet on screen 19 ms
- First display tickFrom window_opened to the first_frame mark. That mark is a callback registered before any frame, and gpui runs such callbacks at the start of the next display-link tick, before that tick's draw — so this phase is the wait between open_window returning and the display's first tick for this window: the window becoming visible and the display link starting. No layout happens inside it; the first frame that reaches the screen is drawn immediately after it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the window is visible and the display link has started; no layout happens here 9 ms
- First frame on screenFrom the first_frame mark to the settled_frame mark, which is registered inside the first_frame callback and therefore fires at the start of the display-link tick after it. Inside: the draw of the first frame that reaches the screen, laid out against the scroll viewport the pre-open draw measured — the content column's real width, which that draw, laid out against the raw window size, did not yet know; this is why the page calls this the settled frame — then its presentation, and the wait for the following tick. By this mark the first settled frame is on screen; the figure includes up to one display refresh of waiting for the tick that stamps it. This mark plus the process-start phase is the headline bar above. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. drawn against the real viewport and presented; marked at the next tick 72 ms
- Notes in vaultHow many .md files the vault scan found in the staged copy of the vault, as the app reported it once the window was up. A new empty vault shows more than zero because qm writes its starter notes into it.
- 1,977
- Blocks in open noteHow many blocks — paragraphs, headings, list items, code fences, blank-line runs — the note that was opened at launch parsed into: what the first frame's layout had to consider.
- 124
- Open note sizeThe size in bytes of the opened note's text as the app holds it in memory.
- 22.2 kB
- Backlinks indexedWhen the backlink index landed: the backlinks_built mark, stamped on the main thread when the background read of every note's link sources finished and its result was installed, in milliseconds from the first statement of main(). It is a moment, not a phase: since 2026-08-17 the window does not wait for it, so it can land after the first frames, and "had not landed when the run ended" means the harness quit the app (which it does shortly after the second frame) before the mark was stamped. Earlier figures, taken when the index was built on the launch path, are not comparable with these.
- had not landed when the run ended, 499 ms after start
One large picture
- Process startTime between the harness spawning the process and the first statement of main(): exec, dyld loading and binding the dynamic libraries the binary links, and the Rust runtime's own start. Computed as the app's start timestamp (taken at the top of main) minus the harness's timestamp from just before it spawned the process. Unlike every phase below, it is not a gap between two timestamps the app takes itself — the app cannot time what happens before it runs — which is why the harness supplies one end of it. exec and dynamic linking, before qm's own code runs 13 ms
- Platform readyFrom the first statement of main() to the platform_ready mark, which the app stamps as the first thing inside gpui's run callback: everything gpui does to bring up NSApplication, the Metal renderer and the text system before any of qm's own code runs. The floor under any launch, separated out so qm's share of startup is not confused with it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. gpui brings up NSApplication, Metal and the text system 69 ms
- UI readyFrom platform_ready to the ui_ready mark: gpui-component's initialisation, the theme, registering every key binding and building the application menu. Nothing has been read from the vault yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. theme, key bindings and the menu bar 18 ms
- Scan vaultFrom ui_ready to the vault_scanned mark, stamped when the vault folder has been walked and every .md file indexed. Three things happen inside it, in order. First, the window's saved geometry is read. Second, gpui creates the macOS window and its Metal renderer — open_window makes the window before it builds the view inside it, and it is while building that view that the vault is scanned. Third, the scan itself, which reads directory entries only (file names and folders, skipping hidden ones), never the contents of a note; note text is read later, by opening a note and by the backlink index in the background. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the macOS window is created, then the tree is walked and every .md file indexed 71 ms
- Open first noteFrom vault_scanned to the first_file_open mark, stamped once the workspace has read the note it opens at launch (the last note open in this vault, or the note a new vault is given), parsed it into blocks, and placed the caret where it last was. Reading the file and building the markdown block map are both inside it; so is recording the vault in the known-vaults list; the background read for the backlink index is started here but not waited for; nothing has been painted yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. read the file, parse it into blocks 28 ms
- Window readyFrom first_file_open to the window_opened mark, stamped when gpui's open_window returns. Inside it: the rest of building the view tree once the first note is ready (the root view that wraps the shell, the close-button handler, the benchmark's own frame callbacks), and then the first layout and paint of the note. gpui draws a window once — into a scene that is not yet presented — before open_window returns, so this phase holds the first time every visible block is laid out and the first glyphs are rasterised. That first layout is against the raw window size; the content column's real width is only known once this draw has measured its scroll viewport, which is what the next drawn frame uses. The macOS window itself was created earlier, inside the Scan vault phase. Nothing is on screen yet. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the view tree is finished and the note is laid out and painted once, not yet on screen 25 ms
- First display tickFrom window_opened to the first_frame mark. That mark is a callback registered before any frame, and gpui runs such callbacks at the start of the next display-link tick, before that tick's draw — so this phase is the wait between open_window returning and the display's first tick for this window: the window becoming visible and the display link starting. No layout happens inside it; the first frame that reaches the screen is drawn immediately after it. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. the window is visible and the display link has started; no layout happens here 10 ms
- First frame on screenFrom the first_frame mark to the settled_frame mark, which is registered inside the first_frame callback and therefore fires at the start of the display-link tick after it. Inside: the draw of the first frame that reaches the screen, laid out against the scroll viewport the pre-open draw measured — the content column's real width, which that draw, laid out against the raw window size, did not yet know; this is why the page calls this the settled frame — then its presentation, and the wait for the following tick. By this mark the first settled frame is on screen; the figure includes up to one display refresh of waiting for the tick that stamps it. This mark plus the process-start phase is the headline bar above. Like every launch phase after Process start, this is the gap between two timestamps the app takes itself ("marks"), counted from the first statement of main(); the harness turns consecutive marks into phase costs. drawn against the real viewport and presented; marked at the next tick 67 ms
- Notes in vaultHow many .md files the vault scan found in the staged copy of the vault, as the app reported it once the window was up. A new empty vault shows more than zero because qm writes its starter notes into it.
- 1
- Blocks in open noteHow many blocks — paragraphs, headings, list items, code fences, blank-line runs — the note that was opened at launch parsed into: what the first frame's layout had to consider.
- 2
- Open note sizeThe size in bytes of the opened note's text as the app holds it in memory.
- 53 B
- Backlinks indexedWhen the backlink index landed: the backlinks_built mark, stamped on the main thread when the background read of every note's link sources finished and its result was installed, in milliseconds from the first statement of main(). It is a moment, not a phase: since 2026-08-17 the window does not wait for it, so it can land after the first frames, and "had not landed when the run ended" means the harness quit the app (which it does shortly after the second frame) before the mark was stamped. Earlier figures, taken when the index was built on the launch path, are not comparable with these.
- 285 ms after start, in the background
Launch — first one after you turn the Mac on
The same launches, with the file cache emptied first — purge(8), which Apple documents as the way to “approximate initial boot conditions with a cold disk buffer cache for performance analysis”. Nothing about qm is warm here: not the vault, not the app's own binary. This is the launch you get once a day, and for an app whose startup is mostly reading files it is the one worth knowing.
- light load (0.33 per core)
- cold file cache
- 3 runs
- Empty vaultThis scenario's cold headline divided by its warm headline from the section above — the two figures in the brackets, as a ratio. Both are medians.
- 6.8× the warm figure (336 ms → 2.3 s)
- Moby DickThis scenario's cold headline divided by its warm headline from the section above — the two figures in the brackets, as a ratio. Both are medians.
- 6.8× the warm figure (335 ms → 2.3 s)
- Large vaultThis scenario's cold headline divided by its warm headline from the section above — the two figures in the brackets, as a ratio. Both are medians.
- 7.8× the warm figure (316 ms → 2.5 s)
Footprint
Memory held with the vault open and the first frames painted, and how big the app is to begin with. The memory figure is macOS's own phys_footprint — the number Activity Monitor shows in its Memory column, and the one the OS uses to decide who is using too much. Resident-set size, which ps reports, is not comparable between processes. The sizes are measured on a build without the benchmark harness, because that is what you download.
- busy load (0.49 per core)
- warm file cache
- 3 runs
- Download (QM.dmg)The size in bytes of the disk image script/make-dmg.sh produced from a release build made without the bench feature — the same commands ./deploy uses — which is the file the download page serves. Code signing and notarisation add a few kilobytes the harness cannot reproduce without the owner's identity, so the published file is very slightly larger than this.
- 7.1 MB
- QM.app on diskThe sum of the sizes of every file inside QM.app as script/make-app.sh builds it from the same no-bench binary: file sizes, not disk blocks, so it is a fact about the app rather than about the disk it sits on.
- 16.1 MB
- The binary itselfThe size in bytes of target/release/qm from a release build without the bench feature: a single stripped, fat-LTO binary, the executable inside QM.app.
- 14.1 MB
Selecting text
Holding Shift and pressing Down, and pressing Cmd+A, in the 1.2 MB note — timed from the keystroke to the frame that shows the highlight, which is the whole of what you wait for. The caret starts halfway down the book, not at the top. The first press of a gesture is its own figure because it is the one that changes what the editor is holding: a selection cannot leave the text its widget has, so the first Shift+Down that reaches past it grows the editor by a line (until 2026-08-18 it handed over the whole note; that cost now belongs to Cmd+A alone, which means the whole note by definition). Every press after it is the steady cost, and it is the one that repeats. Read these in frames. They end at a painted frame, because that is what you actually wait for — so on a 60 Hz display a press whose work fits inside a frame reads as ~17 ms however little it does, and one that misses a vsync reads as ~33 ms. The figure to notice is which side of one frame a press falls on, not the millisecond.
- busy load (0.48 per core)
- warm file cache
- 3 runs
- NoteThe size in bytes of the note the presses were made in, as the app holds it.
- 1.2 MB
- Blocks in the noteHow many blocks the note parsed into — the size of the document the selection is growing through.
- 2,651
- Cmd+A selectedHow many bytes the editor's selection spanned at the end of the Cmd+A run, reported by the app. It is published because a gesture that selected nothing would be fast for the wrong reason; this one selected the note.
- 1.2 MB
- Worst single pressThe longest single press among the later presses (two to twenty) across all the runs: the worst keystroke-to-painted interval observed for Shift+Down.
- 33 ms
Quick open
Typing into Cmd+P re-matches every note in the vault, on the spot — quick open keeps no index and has no debounce, so what the matcher costs is what the keystroke costs. Four queries, because they are four different amounts of work: the empty one, which is the list Cmd+P opens with and which every note matches; a prefix; a substring that has to fall through to the path; and one that matches nothing. Measured in the library rather than through the window, and deliberately: the whole match fits inside a single painted frame at this vault size, so a figure taken from the app would read one frame — about 17 ms — until the day it got forty times worse. This one has no such floor. Before 2026-08-20 the empty query took 4.10 ms here; it takes about 0.13 ms now, and that difference is the point of measuring it this way. Each sample is a batch of matches rather than a single one — at this scale one match timed alone reads the scheduler, not the work.
- busy load (0.69 per core)
- warm file cache
- 5 runs
- Notes in the vaultHow many notes the matcher was run over, which is every note in the vault.
- 1,977
- Rows the list can showHow many rows quick open will show, and so how many the matcher keeps.
- 50
- Recency list depthHow many notes deep the most-recently-opened list was. It ranks ahead of path length, so it is part of the work every note is put through.
- 50
- Matches for the empty queryHow many notes the empty query matched — every one of them, which is why that query is the worst case rather than the cheapest.
- 50
Switching notes
Opening one note after another, timed from the request to the frame that carries the new note — not to the moment the app decided to load it. Two vaults: two copies of the 1.2 MB book, so every switch reads, parses and draws a note the size of Moby Dick, and a vault of about 2,000 ordinary notes, which is what switching feels like in a real vault. The figure goes through the same call every route into a note ends at — quick open, a wikilink, back and forward, the file tree — so one number covers all of them; it does not include Cmd+P's own matching, which is a different question. qm never shows a note's name beside another note's text, and never shows a name over a blank page while it loads: the switch is one step, and a note that cannot be read does not replace the one you are in. Read these in frames, like the selection figures — they end at a painted frame, so on a 60 Hz display anything that fits inside one reads as ~17 ms however little it does.
- busy load (0.80 per core)
- warm file cache
- 3 runs
- Switches per runHow many notes each run opened in turn, after the app had started.
- 20
- Of those, that changed the noteHow many of those requests actually changed the open note. It is published because asking for the note you are already in returns immediately, and a run full of those would be fast for the wrong reason. This equals the number of switches, so every sample is a real one.
- 20
- Note switched into (big vault)The size of the note the last switch landed in, as the app holds it — the check that the big-note run really was switching into the big note.
- 1.2 MB
- Notes in the large vaultHow many notes the large vault held, as the app reported it.
- 1,977
Search
Cmd+Shift+F to results on screen. qm keeps no content index — it reads the files at query time (ADR 0003) on a background thread, after a 150 ms debounce that is included below because you wait for it too. Matches appear as they are found, so the headline figure is time to the first ones; “whole vault scanned” below is when the last note was read, which is the number that grows with the vault. Read the single-note rows with care: results are noticed once per painted frame, and a frame of the 1.2 MB note costs about as long as the jump itself, so those two figures are quantised by the frame cost below and overstate the search work by up to one frame.
- busy load (0.69 per core)
- warm file cache
- 3 runs
Common word, one big note
- QueryThe word searched for, exactly as typed into the search field.
- “whale”
- Notes searchedHow many notes the vault held, as the app reported it — every one of them is read during the scan, because qm keeps no content index.
- 1
- Matches foundHow many matches the scan had found when it completed. The scan stops collecting at 200 (the hit cap), so 200 means "at least 200".
- 1
- Whole vault scannedFrom search_opened to the search_complete mark, stamped on the first painted frame after the background scan reported that it had read the last note — when qm could say "nothing" for a word that appears nowhere. Median of the runs.
- 307 ms
- Jump to next matchAfter the scan completes, the harness jumps from match to match, one jump per painted frame, ten jumps by default; each jump opens the hit's note at the match and paints it. The figure is the median of those frame intervals, pooled across the runs: the steady cost of "next match".
- 21 ms
- Worst jumpThe longest of those match-to-match frame intervals across all the runs.
- 21 ms
Word that appears nowhere
- QueryThe word searched for, exactly as typed into the search field.
- “zzqqxx”
- Notes searchedHow many notes the vault held, as the app reported it — every one of them is read during the scan, because qm keeps no content index.
- 1
- Matches foundHow many matches the scan had found when it completed. The scan stops collecting at 200 (the hit cap), so 200 means "at least 200".
- 0
- Whole vault scannedFrom search_opened to the search_complete mark, stamped on the first painted frame after the background scan reported that it had read the last note — when qm could say "nothing" for a word that appears nowhere. Median of the runs.
- 307 ms
- Jump to next matchAfter the scan completes, the harness jumps from match to match, one jump per painted frame, ten jumps by default; each jump opens the hit's note at the match and paints it. The figure is the median of those frame intervals, pooled across the runs: the steady cost of "next match".
- —
- Worst jumpThe longest of those match-to-match frame intervals across all the runs.
- —
Word that appears nowhere, ~2,000 notes
- QueryThe word searched for, exactly as typed into the search field.
- “zzqqxx”
- Notes searchedHow many notes the vault held, as the app reported it — every one of them is read during the scan, because qm keeps no content index.
- 1,977
- Matches foundHow many matches the scan had found when it completed. The scan stops collecting at 200 (the hit cap), so 200 means "at least 200".
- 0
- Whole vault scannedFrom search_opened to the search_complete mark, stamped on the first painted frame after the background scan reported that it had read the last note — when qm could say "nothing" for a word that appears nowhere. Median of the runs.
- 409 ms
- Jump to next matchAfter the scan completes, the harness jumps from match to match, one jump per painted frame, ten jumps by default; each jump opens the hit's note at the match and paints it. The figure is the median of those frame intervals, pooled across the runs: the steady cost of "next match".
- —
- Worst jumpThe longest of those match-to-match frame intervals across all the runs.
- —
Scrolling — not measured yet
Paging a 1.2 MB note from top to bottom is the one benchmark still outstanding. The
document column is virtualised (ADR 0005) — only the rows in and near the viewport are
laid out — so the traversal no longer lays out every block of the book on every frame;
what it costs now is simply not yet measured. The harness for it is written and lives
behind ./script/bench.py --only scroll; its figures will appear here, and in
the history below, once it has been run end to end.
History
One figure, every run that measured it, in the order they happened — and under each point, what changed in the app between it and the point before. A solid point was taken under light load; a hollow one under busy or heavy load, or with the window covered, and is read with that in mind. The thin line through a point is its min–max across the runs; the point is the median. Newest first in the table.
Selecting text — Shift+Down, every press after the first
26 measurements of this figure across 27 runs logged since 2026-08-17 07:41.
| Date | Git hash | Shift+Down, every press after the first (milliseconds) | Most recent changes |
|---|---|---|---|
| 2026-09-02 22:18 | 0a6394c |
17 ms median of 57 · 16 ms–27 ms busy load (0.38 per core) |
No code change since the run 37 minutes before: the same-hour re-run of launch and select that clears that run's verdicts, with 5996a8b's binary measured the minute before (run 22-18-27, an A/B, off the chart) — select_all 15.4 ms old against 15.6 ms new, pre-main 11–18 ms on both. Nothing that could move a figure changed since 0a6394c — docs and site only. |
| 2026-09-02 21:41 | 0a6394c |
17 ms median of 57 · 16 ms–33 ms busy load (0.48 per core) |
Six days of commits since the 2026-08-27 point: ⇧⌘F is the vault tree with a contents filter (82ec011); a launch asks to be the .md opener, with Don't ask again (0a6394c — a Launch Services query on the background executor, which this bare benchmark binary skips for want of a bundle); One Dark is the default theme (e58d200); ⌥↑/⌥↓ move a line and ⌥⌘digits style it (449e02d, 69ebd72); a press beside a line's text places the caret (209c8c6); an edit near the top and a paste that fits no longer scroll the page (11f6e85, 314ea91); list starts, bold in headings, link hit-testing and a selection's left edge changed how a line is drawn (f6a6f76, e93115f, 8c4bc48, 7ec7e62); the vendored crate's tests joined the suite (dba9d7e). Measured at 21:41 after an evening of builds, the load at 0.6/core as it began, and the three severe verdicts are that regime: select_all's first press (37 ms here) was 15.6 ms on the same binary 37 minutes later and 15.4 ms on 5996a8b's binary the minute before (runs 22-18-27 and 22-18-40, the same-hour A/B); pre-main is dynamic linking before any of qm runs, and both binaries put it at 11–18 ms in that hour; the Rust suite's time doubled under the same load, with more tests in it. The cold group was skipped: no sudo from a detached shell. 21 commits that could move a figure, since 5996a8b
|
| 2026-08-27 08:17 | 5996a8b |
17 ms median of 57 · 16 ms–17 ms light load (0.32 per core) |
The same build on a quiet machine. Its slower figures against 08-22 17:07 are the machine's slow regime, not the code: four A/B runs beside this one (is_default_binary: false) measured binaries from f894903+1 through HEAD at identical times today, and the log shows the same two regimes on near-identical code — 08-22's cold build was 598 s at 09:10 and 230 s at 17:07. Nothing that could move a figure changed since 5996a8b — docs and site only. |
| 2026-08-27 07:59 | 5996a8b |
17 ms median of 57 · 16 ms–18 ms light load (0.33 per core) |
Five days landed: tables — a grid you read, source you edit (ADR 0028), paste as a table (ADR 0031), and selection windows that take them whole; leading-space indents (ADR 0026); Cmd+F; the motion tasks 1150-1200; the privacy posture (ADR 0032). The run itself was contended; the 08:17 re-run is the one to read. 27 commits that could move a figure, since f894903
|
| 2026-08-22 17:07 | f894903 |
17 ms median of 57 · 16 ms–42 ms busy load (0.46 per core) |
Rows off the screen are now counted, not shaped (ADR 0025): select-all on Moby Dick is 9.9 ms, down from 583 the day before — the whole-note handover counts each line's rows from its length and shapes only the screen around the caret, and the footprint after the press falls from 208 MB to 148. select-down is 13 → 4.6 ms for the same reason. The launch, build and switch figures are also lower, but that is the machine: this run was quiet (0.50 load/core) where 40d2a4c was contended (3.2), so read those as the baseline being slow, not this build being fast. The two select figures are the change. Cold-cache figures skipped this run (no sudo). 1 commit that could move a figure, since 40d2a4c
|
| 2026-08-22 14:50 | 40d2a4c |
17 ms median of 57 · 16 ms–60 ms busy load (0.59 per core) |
The editor now wraps by shape_text, the rendered row's own function (ADR 0023), so a paragraph no longer re-wraps when the caret arrives on it; exactness shapes every line on ⌘A, and select-all on Moby Dick shows it at 583 ms against 22 (task 1120 holds the numbers). Also since 84b81dd: QM claims .md files and opens a note that arrives from the Finder (ADR 0021), the status bar says when no vault is open, qm-pixels reads a screenshot's blocks (ADR 0022) and now its rows, and the build stamp and qaqm report build time. The run was contended — load 3.2 across 8 cores — so the launch and test-suite figures are floors, not a best case. 8 commits that could move a figure, since 84b81dd
|
| 2026-08-22 09:10 | 84b81dd |
17 ms median of 57 · 16 ms–25 ms light load (0.32 per core) |
Three commits since 1f80dd3, none on a measured path: About answers from every screen, the picker included (30f183a); the binary stamps whether its inputs had uncommitted edits and answers `qm --build-info`, for the new ./qaqm (97b584f); build.rs asks git where HEAD lives, so a worktree build stops rerunning the stamp (84b81dd). Measured while other sessions were building in worktrees — the machine's 15-minute load average was 14 — so the uniform ~1.4× across launch, search and both build times, pre_main included, is the machine and not the code; the headline figures were left at 1f80dd3's. tests.rust_ms 18.1 → 11.0 s is the same contention in reverse. 46 of 100 steady; re-measure on a quiet machine. 3 commits that could move a figure, since 1f80dd3
|
| 2026-08-21 21:27 | 1f80dd3 |
17 ms median of 57 · 16 ms–25 ms busy load (0.45 per core) |
Three commits since fbca877 (the shipped 8ee1007 is fbca877 plus its benchmark run): a selection that retreats releases the lines it left (f57cac9); a whole-text re-wrap of the editor starts from scratch, so a collapsed window keeps no stale rows — the 'extra spacing' regression the column patch exposed (44efa7f); every frame the document draws is recorded in test builds, with Enter above a list checked frame by frame (1f80dd3). Contended again (0.44/core) despite a two-minute wait: another build was running on the machine. 94 of 100 steady. tests.rust_ms 12.4 → 18.1 s is real, not noise — the new editor-box test walks thirty-two selection gestures (~5 s) and two frame tests joined the suite. select_all_moby's footprint 137 → 168 MB is the same two-state figure select_rows showed this morning. 3 commits that could move a figure, since fbca877
|
| 2026-08-21 15:31 | fbca877 |
17 ms median of 57 · 16 ms–18 ms light load (0.32 per core) |
Three commits since 1904806: Copy Path copies one shell word (aece833); a picture's corner drags it to a new size, the width written into the link as Obsidian's pipe (f3686b0); a selection across list items leaves every item where it was — the vendored Input takes a column per line, qm hides the markers in a grown window and overlays the chrome (fbca877). A quiet machine this time (0.17/core): the eleven launch-phase figures last night's contended run flagged are all back to 0.5–0.8× of it, which clears them as contention, not code. The one REGRESSED pair, select_rows_moby footprint 129 → 227 MB, is that figure's two states showing again (227 MB in b3ad404, 129 in 1904806, 227 here); a note of prose exercises none of today's list or picture code. 3 commits that could move a figure, since 1904806
|
| 2026-08-20 23:07 | 1904806 |
17 ms median of 57 · 16 ms–18 ms busy load (0.44 per core) |
Sixteen commits from several sessions. Product: a note opens off the drawing thread and lands whole (ADR 0018); quick open stops re-sorting the vault on every keystroke (1010 §2); hidden decoration ranges snap to character boundaries (1010 §3); the [[ popup and Enter-in-a-list fixes; and six picker/editor tickets — a What-is-a-vault card, Copy Diagnostics on every screen, the wordmark in Helvetica Neue, a guarded-folder explanation for an iCloud vault, and a per-line hanging indent in the vendored Input, whose wrapper now asks line_chrome for each line on a re-wrap. Harness: the pinned corpus (1050), the first-press gesture benchmark (1040), batched quick-open timing, and the build-cache prune in every builder. Measured contended — load 3.1 across 8 cores after the 180 s wait, with other sessions building — so the 1.5–1.7× launch-phase and first-press figures are the machine, not a change; re-run quiet to clear them. 16 commits that could move a figure, since b3ad404
|
| 2026-08-20 19:31 | b3ad404 |
17 ms median of 57 · 16 ms–26 ms busy load (0.45 per core) |
Forward delete at a line's end joins the line below (b3ad404), through edit_document — one editor change, nothing on the launch, search or quick-open paths. 1 commit that could move a figure, since dcba9e2
|
| 2026-08-20 19:18 | dcba9e2 |
17 ms median of 57 · 16 ms–33 ms busy load (0.36 per core) |
Quick open is timed in batches (dcba9e2): the per-keystroke figure was a tenth of a millisecond, under what the clock can tell apart, so the harness times a run of keystrokes and divides. A measurement change, not a product change. 1 commit that could move a figure, since 1125002
|
| 2026-08-20 19:03 | 1125002 |
17 ms median of 57 · 16 ms–17 ms light load (0.32 per core) |
Nothing — the third of three serial runs of the same commit. Together they showed quick_open_vault2k moving 2.9x on an unchanged binary while every app figure held to 1.05x: the matcher answers in tenths of a millisecond and was being timed one match at a time, so the figure was reading the scheduler. Fixed in the next commit by batching; these three are the evidence. Nothing that could move a figure changed since 1125002 — docs and site only. |
| 2026-08-20 18:52 | 1125002 |
17 ms median of 57 · 16 ms–26 ms busy load (0.36 per core) |
Nothing — the second of three serial runs of the same commit, to see what the suite's own repeatability is now the corpus is pinned. The app figures agree to 1.00-1.05x; quick_open does not, which is what sent me back to the harness. Nothing that could move a figure changed since 1125002 — docs and site only. |
| 2026-08-20 18:42 | 1125002 |
17 ms median of 57 · 16 ms–18 ms light load (0.33 per core) |
Quick open stopped re-sorting the whole vault per keystroke (1010 §2); hidden decoration ranges snap to char boundaries (1010 §3); the 2,000-note corpus became a pinned artifact fetched from Tigris and verified by digest (1050). First run to record which corpus it measured. First of three serial runs of this commit. 3 commits that could move a figure, since febb754
|
| 2026-08-20 15:01 | febb754 |
17 ms median of 57 · 16 ms–18 ms light load (0.34 per core) |
ADR 0018 step 4: the note read and its block map moved off the drawing thread (closes task 1010 §1). The steady switch cost did not move — 16.69 and 16.66 ms, still one frame — so the note arrives in the very next painted frame even though the read now crosses to another thread and back. first_switch_ms moved both ways again (+18.9% moby, -12.1% vault2k); that figure is one sample per run with a ~10 ms spread and is not readable at three repeats. Launch stays synchronous by design. No cold figures: no TTY for sudo. 1 commit that could move a figure, since 0c7ca69
|
| 2026-08-20 08:42 | 0c7ca69 |
17 ms median of 57 · 16 ms–18 ms busy load (0.37 per core) |
ADR 0018 steps 1-3: a note's name and its text became one value, and opening one became load-then-commit. A note qm cannot read no longer opens as an empty one. Refactor only — the read is still synchronous — and the steady switch cost is unchanged at 16.7 ms on both corpora. No cold figures: no TTY for sudo. 1 commit that could move a figure, since 9086bff
|
| 2026-08-20 08:06 | 9086bff |
17 ms median of 57 · 16 ms–18 ms busy load (0.58 per core) |
Added the note-switch benchmark itself (QM_BENCH=switch), so the gesture the app exists to get right finally has a figure. No app change: this is the baseline the ADR 0018 work is measured against. switch_moby and switch_vault2k both land on 16.7 ms — one frame at 60 Hz. 6 commits that could move a figure, since 7331c7c
|
| 2026-08-19 22:48 | 7331c7c |
17 ms median of 57 · 16 ms–18 ms busy load (0.36 per core) |
Six editor and shell commits, none on the launch path: list selections stop at an item's text so type/paste/delete keep the marker, triple-click selects the line, the active-line highlight skips blank rows, eleven light/dark themes behind a new Settings screen, and the shortcut help sheet. Run measured contended (load 1.69/core) — the uniform launch/search slowdowns are load, to be cleared by a quiet re-run. 6 commits that could move a figure, since b6cd7c3
|
| 2026-08-19 09:22 | b6cd7c3 |
17 ms median of 57 · 16 ms–18 ms light load (0.32 per core) |
One commit on a figure path, and it is the measuring apparatus, not the app: the footprint reader no longer throws a memory reading away when the kernel's lifetime maximum lags the present by a page (f91ef2a), so the select-all memory figure has three samples again — 130, 130 and 147 MB, median 130 — where the previous runs had one each; the 215 MB of the run before was that single sample, not a change in the app. The shipped binary and bundle are identical to the byte to the 09:00 run's; the download figure still moved, 6.68 to 6.97 MB, which is the disk image's own wobble on identical contents (task 0950) and not the app. Everything else steady. 1 commit that could move a figure, since dd9d92b
|
| 2026-08-19 09:00 | dd9d92b |
17 ms median of 57 · 16 ms–18 ms light load (0.33 per core) |
One app change, and it changes what the empty-vault launch measures: a brand-new vault's welcome note now ends with a picture — Schiele's still life at 480×720, carried in the binary and written into the vault exactly as a paste would write it, under attachments/Pasted image <stamp>.jpg — so launch_empty opens a 17-block, 1.6 kB note with a JPEG to resolve and decode where it opened a 13-block, 1.1 kB one (dd9d92b); its document_bytes, footprint and first-paint figures move for that reason, and the binary is 96 KB larger. Nothing else on a measured path changed. The select-all memory figure, 215 MB against 147 MB, is one sample against one sample: the app had been discarding two of every three footprint readings on that scenario because the kernel's lifetime-maximum lags the present by a page when memory is growing at the instant of the read, and a sanity check mistook the lag for a struct-layout error — found while reading this run, fixed in f91ef2a; from the next run the figure has three samples and a spread. 1 commit that could move a figure, since 2678a58
|
| 2026-08-19 08:23 | 2678a58 |
17 ms median of 57 · 16 ms–18 ms light load (0.34 per core) |
No change to the app: the only commit on a figure path is the harness gaining its tests group (2678a58), which adds two figures — the Rust suite and the pixel suite, each timed as the command a developer types when nothing needs compiling — and changes nothing that was already measured; this run is their first point, with no backfill behind it. The binary and bundle are byte-identical to the 08:06 run's (same sizes to the byte), so the one figure that moved, the download at 6.79 MB against 6.57 MB, is the disk image's compression of identical contents, not the app. Quiet machine (0.29 per core); 81 of 82 figures steady against the run 17 minutes earlier. 1 commit that could move a figure, since a45db7f
|
| 2026-08-19 08:06 | a45db7f |
17 ms median of 57 · 16 ms–17 ms busy load (0.41 per core) |
One commit on a figure path, and it is not on any measured path: a fourth item on a picture's right-click menu, Show in Finder (6245b07) — a menu action, no change to launch, layout, search or selection. The harness is unchanged; this run exists because the owner's rule measures a free machine after every ticket. So the ~30% drop across nearly every figure against the 05:45 run — launch phases that share no code, search, the time before main() and even the cold build (445 s to 212 s, same compiler, same sources) — is the machine, not the app: the 05:45 run began at a load of 4.3 across 8 cores, minutes after a deploy; this one began at 3.3 and was let settle to 2.7 before measuring. Two runs of the same code 2.5 hours apart are the noise floor of this Mac, and the reason a single point is never a regression. 1 commit that could move a figure, since d1f547f
|
| 2026-08-19 05:45 | d1f547f |
17 ms median of 57 · 16 ms–18 ms light load (0.26 per core) |
One app change: pasting a picture now writes Obsidian's file name (Pasted image YYYYMMDDHHMMSS.png, with a counter on collision) and Obsidian's embed spelling ![[attachments/…]]; to make that safe, wikilink image targets resolve from the vault root rather than against the note, ImageLink records which dialect wrote a target so each resolves by its own rule, and a wikilink target is no longer percent-decoded, while  is read as before (2bc0c7c). That is the path that resolves and draws a picture in a note — exactly what the new launch_image scenario opens, and launch_image is the only group this run measured: the harness change that added it, and the 13 MB Schiele corpus it opens, were committed straight after as a7d78b6. Every other group in this run is carried over from the f2eda09 run and is not a measurement of this commit's binary. The harness gained a corpus and a group: a 13 MB, 75-megapixel JPEG staged as a one-note vault with ![[attachments/…]] the way qm's own paste writes it, measured as launch_image (a7d78b6). Three changes alter what a launch figure means. The vault index: resolving one wikilink walked every path in the vault, so building the backlink index cost notes × links; resolution is now a name map decided once per change of vault membership, autosave no longer rebuilds the reverse map, and the read of every note's source moved to a background task whose landing fires backlinks_built — so backlink_index_ms is no longer a step on the way to first paint, and the phases after it no longer include the index (bb3d2e6). An empty vault now opens on a seeded demonstration note — lists, headings, an embed and a wikilink, plus the note it embeds — so launch_empty writes two files and renders a 13-block note where it rendered a one-line welcome (notes 1→2, blocks 2→13, 31→1130 bytes; 0a72c94). And every hard line is now a row: a blank line is an empty row, block structure adds no spacing, a mouse drag grows the editor over the lines it reaches instead of taking the note, and a heading inside a grown window draws at body size — which changes the on-screen height of every note (6f5faef, ADR 0014). 15 commits that could move a figure, since f2eda09
|
| 2026-08-17 17:22 | f2eda09 |
17 ms median of 57 · 16 ms–18 ms light load (0.32 per core) |
Nothing in this interval touches the app, the vendored toolkit, the harness or the bundle: the binary measured here is built from the same sources as the one at 4ad2d68 (both runs report the same binary size). The four commits are bookkeeping around the benchmark itself — recording the 4ad2d68 run (250f1d5), filing task 0860 because the run-to-run comparison had reported a definition change as a speedup (c5bf2ff), archiving finished tasks (2ac93c8), and deleting the eighteen earlier runs measured under superseded definitions (f2eda09). With identical sources on both sides, any difference between these two runs is conditions, not code: the 4ad2d68 run carried a load qualifier and had no cold-cache pass; this one was quiet and the first with cold-cache figures. Nothing that could move a figure changed since 4ad2d68 — docs and site only. |
| 2026-08-17 07:41 | 4ad2d68 |
17 ms median of 57 · 16 ms–18 ms busy load (0.83 per core) |
First measurement of this figure. |