Skip to content

perf(compile): faster discovery, scope visibility and warm writes - #930

Merged
kvlonge merged 4 commits into
mainfrom
perf/discovery-scopes-write
Oct 4, 2026
Merged

kvlonge merged 4 commits into
mainfrom
perf/discovery-scopes-write

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Why

Profiling 0.133.0 showed discovery walking the project tree about 40 times per compile, scope visibility checking every resource against every declaration (11x more calls from 3k to 10k models), and warm writes doing a stat plus a read per artifact and trying to remove thousands of non-empty folders. The docs also implied [defaults] could describe models and didn't say that only the nearest path default applies (CHI-441 phase 2).

Changes

  • Index scope visibility by declaration scope and owner; compile callers use a lighter resolver that doesn't build reasons.
  • Each discovery pass reads every directory listing once into a snapshot that answers all globs, and MODEL headers are matched natively in one batch.
  • Compare each warm artifact with one open, and prune only folders that pruning emptied.
  • Docs: which path default applies to a model, and the correct [defaults] wording.

Verification

  • Same-machine medians, wall s (base → head): dense 10k cold 49.4 → 47.0, warm 23.7 → 21.2; dense 3k warm 7.5 → 7.0; ~1,400-model project warm 7.4 → 6.8. Output byte-identical across all 72 runs.
  • Equivalence tests: visibility against an exhaustive reference classifier, snapshot globs against Path.rglob on random trees with links, the native header matcher against the regex on fuzzed input, and real-CLI write and prune tests.
  • One independent review; make test, make check-ci.

Group declarations by scope and owner once per scope lookup so a
resource's visible declarations come from owner and ancestor lookups
instead of classifying every declaration. Compile callers only need the
identities of inaccessible declarations, so they use a resolver that
skips per-pair inaccessible reasons. The full resolver keeps its output.
…vely

Discovery queried the project tree with dozens of recursive globs per
compile: once per declaration kind, once per layout validator, and once per
resource category. A discovery pass now reads each directory listing once
into a scoped snapshot that answers every glob with pathlib's matching and
symlink semantics, memoizes the declaration facts and group layout, and
classifies scoped-tree membership once per directory. The snapshot lives
only for one pass, so later passes see current files.

MODEL headers are matched by an exact native equivalent of the possessive
header pattern in one batch call.
…y folders

Warm writes compared every compiled artifact with a stat followed by a
separate read; they now open each file once. The stale-file walk tried to
remove every compiled directory and relied on failing rmdir calls for
non-empty ones; it now removes only folders left empty by pruning, with
the same result. Unchanged artifacts keep their bytes and mtimes.
The format page said a model described by [defaults] passes the
description check, but [defaults] rejects description with D001. Project
configuration now documents that each model uses only its nearest matching
path default, that path defaults are not merged, and how to describe models
when a broad "**" key coexists with more specific keys.
@kvlonge
kvlonge enabled auto-merge (squash) October 4, 2026 03:15
@kvlonge
kvlonge merged commit 3a12e4e into main Oct 4, 2026
36 checks passed
@kvlonge
kvlonge deleted the perf/discovery-scopes-write branch October 4, 2026 03:42
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.

1 participant