Skip to content

perf(files): background load, parse-free clock scan - #1182

Merged
kristijanhusak merged 2 commits into
nvim-orgmode:masterfrom
seflue:perf/defer-agenda-files-load
Sep 18, 2026
Merged

kristijanhusak merged 2 commits into
nvim-orgmode:masterfrom
seflue:perf/defer-agenda-files-load

Conversation

@seflue

@seflue seflue commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

ref #1048, replaces #1073

Opening a single org file waits until every agenda file has been read. The startup clock scan walks the headlines of every file, and that parses each one with tree-sitter. With a few hundred org-roam files that is seconds of blocked editor.

The parse is the expensive part, not the read; deferring the reads alone does not help. Two independent commits:

The clock scan parses only candidate files. A running clock is a CLOCK line with a single timestamp and nothing after it. A raw line scan picks the files that have one; only those get parsed, and the parser still decides. Archive files are skipped.

Loading moves to the background. Org:init no longer waits for files:load(); Clock:init chains on the load promise, and a second load() call during loading gets that promise instead of blocking. Callers that need all files still wait at ensure_loaded. The cost moves to the first agenda open.

Opening a single org file blocked until every agenda file was
read. The startup clock scan walks the headlines of all files,
which forces a treesitter parse of each one, so time to
interaction grew with the size of the agenda.

Loading now starts in the background and the startup clock scan
waits for it instead of blocking. Callers that need all files
still wait at the existing barrier, so the cost is paid when the
agenda is opened rather than when a file is edited.

The alternative, keeping the scan synchronous and only deferring
the file reads, was dropped because the parse is the expensive
part.
Finding the active clock parsed every agenda file with
treesitter. That parse blocked the event loop on startup and
after every clock action, longer with each added file.

A cheap pre-filter now picks the files worth parsing: a raw text
scan for CLOCK lines that could still be open. False positives
will be caught by the parser, which decides what is clocked in.

CLOCK matches case insensitively, so hand written lowercase
entries keep working.
@kristijanhusak

Copy link
Copy Markdown
Member

load_promise looks good, but I'm not sure about the clock line parsing. Do you know how time is saved by parsing the line with regex? With the load_promise change it should be more performant anyway.

@seflue

seflue commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

load() only reads the files. The parse happens in get_clocked_headline. With load_promise alone the scan still parses all of them once the reads finish; the freeze moves from :edit to a moment later. The pre-filter is what removes it.

Measured on 500 generated org-roam style files (1.8 MB, one running clock in the last file), median of 3 runs:

variant open (ms) done (ms) scan (ms) rescan (ms) files parsed
master cd431cb 583 583 470 110 500
load_promise dc52ca2 44 595 470 123 500
load_promise + pre-filter 697e8a5 46 143 11 2 2
  • open: how long :edit of the first org file blocks
  • done: time from :edit until the startup clock scan has finished
  • scan: duration of that scan, the editor is blocked meanwhile
  • rescan: a second scan, every clock in/out/cancel runs one
  • parsed: files the startup scan parsed with tree-sitter

Script: orgbench.lua. nvim -l orgbench.lua writes the fixture, checks out the three commits as worktrees next to the script and prints the table, --files 2000 scales it. To feel it: nvim -u orgbench/init-master.lua orgbench/fixture/note-0001.org, same with init-prefilter.lua. Vibe-coded with Claude's help.

@kristijanhusak

Copy link
Copy Markdown
Member

Ok, thanks for the benchmark. We can go with it for now, but I'm hoping to find a better solution in the future for tracking clocked in headline.
Thanks for the PR!

@kristijanhusak
kristijanhusak merged commit cd6875d into nvim-orgmode:master Sep 18, 2026
47 of 49 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.

2 participants