Skip to content

perf(format): format files in parallel and skip unchanged files - #1183

Merged
kvlonge merged 6 commits into
mainfrom
perf/fast-format
Oct 20, 2026
Merged

kvlonge merged 6 commits into
mainfrom
perf/fast-format

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Why

sqb format --check on a 10,000-model project (12,690 files) took 21–25 s and about 324 G instructions. Header work ran serially on one thread, and every run formatted every file again, so pre-commit and CI paid the full cost even when nothing had changed.

Changes

  • File texts stay in Rust between passes instead of going through Python dictionaries. Each text's header lint is reused for the final report.
  • Header parsing no longer starts a thread pool for every header. The header scanner no longer allocates for each line.
  • The formatter reuses token and oracle lexemes across its verification and idempotency passes.
  • Per-file stages run on a 16 MiB-stack worker pool in file order. Replacements are staged in parallel, then renamed in path order; the earliest failure leaves it and later files untouched and removes leftover temporaries. Python steps stay serial.
  • Files found formatted and fault-free are stored in target/cache/compiler-native-v1/format-results.bin. The key covers relative path, text, format settings, dialect and build. --fix and fixture-dependent test files bypass the store; --no-cache formats every file.

Verification

  • Same machine, base vs head (load 4–8):
    • 10k project, cold --check: 324 G → 240 G instructions, 21–25 s → 10–11 s wall.
    • Formatted 10k project, warm: 5.6 G instructions, 0.43 s wall.
    • 1k project: 48.5 G → 38.0 G cold, 0.19 s warm.
  • Outputs of --check, --diff and --json, cold and warm, are byte-identical to base on the 1k and 10k projects and a fault-heavy variant. Written trees of all example projects are also identical.
  • New integration and E2E tests cover cache hits, edits, settings, a build change, a damaged store, --no-cache, serial vs parallel output, and a failed write leaving later files untouched.
  • Format test suites and the format performance guards pass. make compiler-differential gives 373/373, and make check-ci passes.

…and tokenization

Format runs now hold file texts in Rust between passes instead of
round-tripping every file through Python dictionaries, reuse each text's
header lint for the final report, skip per-line keyword allocations in
the header scanner, and reuse token and oracle lexemes across the
formatter's verification and idempotency passes.
Header lint, header formatting, body preparation and splicing, rewrite
checks, suppression scanning and file writes now run across a format
worker pool with 16 MiB stacks. Results are collected in file order and
the first failure in file order is reported, so output, ordering and
exit codes do not depend on scheduling. Python work (fixture contracts,
Rule fixes, description lookups and report records) stays on the
calling thread.
sqb format now remembers every file it found already formatted with
nothing to report, in target/cache/compiler-native-v1/format-results.bin.
Entries are keyed by the file's project-relative path, its exact text,
the effective format and rule threshold settings and dialect, and the
SQLBuild build, so an edit, a settings change or another build formats
the file again; a damaged store opens empty. --fix never uses the cache,
SQL test files are not skipped while fixture-null formatting reads model
contracts, and the new --no-cache flag formats every file.
…ailure

Formatting stays parallel, but files are replaced in path order again
and a failed write leaves every later file as authored. The --no-cache
help text now matches the other format flags.
…ath order

Replacement files are written and synced beside their targets in
parallel, then renamed in path order. The earliest file whose staging or
rename fails stops the run: it and every later file keep their text, and
every unused replacement file is removed.
@kvlonge
kvlonge enabled auto-merge (squash) October 20, 2026 22:29
@kvlonge
kvlonge merged commit 1f39065 into main Oct 20, 2026
86 of 88 checks passed
@kvlonge
kvlonge deleted the perf/fast-format branch October 20, 2026 23:41
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