Skip to content

Commit 693e86e

Browse files
Khaclaude
andauthored
perf: load --incr-load snapshot deps per module (#14023)
This PR makes `--incr-load` reload a snapshot's dependency `.olean*` regions grouped by module, so each `CompactedRegion.read` only sees the sibling variants it can actually point into rather than the full, growing list of all previously-loaded regions. For an `import Mathlib` snapshot this takes warm load time from ~30 s to ~0.8 s (now faster than a fresh `import Mathlib`). A regular `.olean*`'s only cross-region pointers are to the prior variants of the same module (`.olean` <- `.olean.server` <- `.olean.private`); cross-module references go through the constant map, not direct region pointers. The old loader passed the whole growing `depRegions` array to every read, so each read extracted and sorted an `O(N)` dependency-view vector, i.e. `O(N²)` across the ~37k regions of Mathlib, dominating load time. The `<snap>.deps` sidecar is reshaped from a flat path list into a JSON `Array ModuleArtifacts` (reusing the `--setup` artifact type), reconstructed from `env.header.regions` at save time by `regionsToModuleArtifacts`. The loader reads each module's variant chain with only its own siblings as deps, reads any `.ir` region with no deps (as regular import does), and passes the full accumulated set only to the single final read of the snapshot region itself. Co-authored-by: Claude <noreply@anthropic.com>
1 parent c2b551f commit 693e86e

2 files changed

Lines changed: 50 additions & 13 deletions

File tree

src/Lean/Elab/Frontend.lean

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,52 @@ private structure IncrSnapshot where
155155
snap : Language.Lean.InitialSnapshot
156156
initModIdxs : Array Nat
157157

158+
/--
159+
Assembles `ModuleArtifacts`, the `--incr-save` helper file's format, from flat regions so that
160+
loading can be optimized. This is a subset of `.setup.json` but we don't want to demand `--setup`
161+
being used with save, so we reconstruct the needed information here.
162+
-/
163+
private def regionsToModuleArtifacts (regions : Array CompactedRegion) : Array ModuleArtifacts :=
164+
Id.run do
165+
-- base `.olean` path (as string) → its `ModuleArtifacts`, plus first-seen order for stability
166+
let mut order : Array String := #[]
167+
let mut byBase : Std.HashMap String ModuleArtifacts := {}
168+
for region in regions do
169+
let p := region.filePath
170+
let (base, upd) : String × (ModuleArtifacts → ModuleArtifacts) :=
171+
match p.extension with
172+
| some "server" => (p.withExtension "" |>.toString, fun a => { a with oleanServer? := p })
173+
| some "private" => (p.withExtension "" |>.toString, fun a => { a with oleanPrivate? := p })
174+
| some "ir" => (p.withExtension "olean" |>.toString, fun a => { a with ir? := p })
175+
| _ => (p.toString, fun a => { a with olean? := p })
176+
unless byBase.contains base do
177+
order := order.push base
178+
byBase := byBase.insert base (upd (byBase.getD base {}))
179+
return order.map (byBase[·]!)
180+
158181
/-- Loads a snapshot saved by `--incr-(header-)save`. -/
159182
private unsafe def loadIncrSnapshot (fname : System.FilePath) :
160183
IO IncrSnapshot := do
161184
let depsFile := fname.addExtension "deps"
162-
let depPaths : Array System.FilePath := (← IO.FS.lines depsFile).map (⟨·⟩)
163-
let mut depRegions : Array CompactedRegion := #[]
164-
for p in depPaths do
165-
-- `depRegions` must be passed for `.olean <- .olean.server <- ...` pointer reuse.
166-
let (_, region) ← CompactedRegion.read (α := ModuleData) p depRegions
167-
depRegions := depRegions.push region
168-
let (data, _region) ←
169-
CompactedRegion.read (α := IncrSnapshot) fname depRegions
185+
let moduleArts : Array ModuleArtifacts ←
186+
match Json.parse (← IO.FS.readFile depsFile) >>= fromJson? with
187+
| .ok arts => pure arts
188+
| .error e => throw <| IO.userError s!"failed to parse snapshot deps file {depsFile}: {e}"
189+
let mut depRegions : Array CompactedRegion := Array.emptyWithCapacity (moduleArts.size * 4)
190+
for arts in moduleArts do
191+
-- A module's `.olean` variants only point into the prior variants of the same module, so read
192+
-- the chain with just its own siblings as deps.
193+
let mut chainDeps : Array CompactedRegion := #[]
194+
for partPath in arts.oleanParts do
195+
let (_, region) ← CompactedRegion.read (α := ModuleData) partPath chainDeps
196+
chainDeps := chainDeps.push region
197+
depRegions := depRegions ++ chainDeps
198+
-- IR regions carry no cross-region pointers (loaded with no deps in regular import).
199+
if let some irPath := arts.ir? then
200+
let (_, region) ← CompactedRegion.read (α := ModuleData) irPath #[]
201+
depRegions := depRegions.push region
202+
-- The snapshot region itself references every loaded dep region.
203+
let (data, _region) ← CompactedRegion.read (α := IncrSnapshot) fname depRegions
170204
return data
171205

172206
/--
@@ -249,17 +283,16 @@ def runFrontend
249283
let finalOpts := cmdState.scopes[0]!.opts
250284

251285
-- Saves `snapToSave` wrapped with the init-mod indices used by `runInitAttrsForModules` on load.
252-
-- Writes a `<incrFile>.deps` helper alongside: the dep paths parallel to `env.header.regions`,
253-
-- needed to map the snapshot back in before we can access `env`.
286+
-- Writes a `<incrFile>.deps` JSON helper alongside: the dep regions grouped per module (see
287+
-- `regionsToModuleArtifacts`), needed to map the snapshot back in before we can access `env`.
254288
let saveSnap (incrFile : System.FilePath) (snapToSave : Language.Lean.InitialSnapshot) :
255289
IO Unit := do
256290
let toSave : IncrSnapshot :=
257291
{ snap := snapToSave, initModIdxs := getRegularInitAttrModIdxs env }
258292
let compactor ← (unsafe CompactedRegion.save incrFile `_snap toSave
259293
env.header.regions none (allowClosures := true))
260-
let depPaths : Array System.FilePath := env.header.regions.map CompactedRegion.filePath
261-
IO.FS.writeFile (incrFile.addExtension "deps") <|
262-
String.intercalate "\n" (depPaths.toList.map (·.toString))
294+
let moduleArts := regionsToModuleArtifacts env.header.regions
295+
IO.FS.writeFile (incrFile.addExtension "deps") (toJson moduleArts).compress
263296
Runtime.forget compactor
264297

265298
-- save full incremental snapshot for next invocation

tests/compile_bench/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Tests creating temporary output files should prefix them with `_tmp_` so they're all
2+
# covered by this one pattern. The tests delete them on success; this entry catches stragglers
3+
# from failed runs.
4+
/_tmp_*

0 commit comments

Comments
 (0)