perf: store per-module constant prefix trees in oleans#14362
Conversation
|
!bench |
|
Benchmark results for 7a68001 against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (55✅, 329🟥) Too many entries to display here. View the full report on radar instead. Medium changes (7✅, 1528🟥) Too many entries to display here. View the full report on radar instead. Small changes (5✅, 816🟥) Too many entries to display here. View the full report on radar instead. |
|
Reference manual CI status:
|
|
!bench mathlib |
|
Benchmark results for leanprover-community/mathlib4-nightly-testing@48fbe78 against leanprover-community/mathlib4-nightly-testing@5edfd4c are in. There are significant results. @Kha
No significant changes detected. |
|
!bench |
|
Benchmark results for 736e8af against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (56✅, 232🟥) Too many entries to display here. View the full report on radar instead. Medium changes (5✅, 1101🟥) Too many entries to display here. View the full report on radar instead. Small changes (6✅, 1324🟥) Too many entries to display here. View the full report on radar instead. |
This PR replaces the eager construction of the imported-constants hash maps in `finalizeImport` (`privateConstantMap`, `publicConstantMap`, `const2ModIdx`; ~530k insertions for `import Lean`) with prefix trees over each module's constant names that are precomputed at olean write time and remain in the memory-mapped compacted regions. Import now only merges the per-module trees, allocating fresh nodes solely for name prefixes shared between modules (~3k nodes / ~81k edges for the stdlib), and lookups walk the merged view, disambiguating children by cached per-prefix `Name` hash. `import Lean` drops from ~527ms to ~393ms; olean sizes grow by ~6%. Details: * New `Lean.ConstTrie`: generic per-module tree (`ConstTrie α`), merged import view (`ImportedConsts α`) with single-module subtrees borrowed from the regions and module-index tagging, and the new `ConstMap` structure (`imported` view + `locals` map). * `ModuleData` gains `constTrie`/`extraConstTrie` fields, filled centrally in `saveModuleData(Parts)`. The extra-const tree replaces the 325k codegen-name entries of `const2ModIdx` (`Kernel.Environment` now holds `allImportedConsts`/`importedExtraConsts` instead, serving `getModuleIdxFor?`). * Duplicate imported constants are resolved (incl. `subsumesInfo` realizability subsumption and `throwAlreadyImported`) from the merge's reported collisions. * In `saveModuleDataParts`, all parts' data must stay alive across the chained compactor saves; the tries are therefore attached to all parts up front (the compactor tracks compacted objects by heap address, so per-part temporaries would corrupt later parts via address reuse). * `stage0/src/stdlib_flags.h` temporarily enables `interpreter.prefer_native` as this is an ABI-breaking change to `Kernel.Environment`/`ConstMap` (to be reverted by `update-stage0`). Stage 1 tests are meaningless on this commit since the stage 1 oleans lack the new `ModuleData` fields; validate against stage 2. Co-Authored-By: Claude
This PR speeds up constant lookups in the prefix-tree-backed environment by storing each trie node's child key hashes in a `ByteArray` alongside the children, so the per-level binary search runs over contiguous memory instead of chasing child and key pointers, in the memory-mapped per-module trees as well as the merged import spine. This supersedes the `indexed` hash-map wrappers on wide merged nodes, which could not cover wide nodes inside single-module region subtrees and made `Runtime.markPersistent` walk their entries on every import. Co-Authored-By: Claude
|
!bench |
|
Benchmark results for 2abb3e2 against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (49✅, 433🟥) Too many entries to display here. View the full report on radar instead. Medium changes (9✅, 1889🟥) Too many entries to display here. View the full report on radar instead. Small changes (4✅, 369🟥) Too many entries to display here. View the full report on radar instead. |
This PR restores the intended final form of the trie child index, which the previous commit accidentally carried in an interim form due to a workspace snapshot race: instead of a binary search that composes each probed hash from eight byte loads, the child index stores `[k][2^k + 1 u16 offsets][one fingerprint byte per child]`; the query hash's top `k` bits select a window of on average at most one child, a fingerprint byte filters it, and the final name component comparison (sound on its own, as siblings always differ in their final component) confirms the match. Co-Authored-By: Claude
|
!bench |
|
Benchmark results for e957cab against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (52✅, 126🟥) Too many entries to display here. View the full report on radar instead. Medium changes (7✅, 560🟥) Too many entries to display here. View the full report on radar instead. Small changes (6✅, 1925🟥) Too many entries to display here. View the full report on radar instead. |
The compat accessor recomputed the map by folding over all imported constants on every call. `Batteries.Tactic.Lint.getDeclsInPackage` reads it inside a per-declaration fold, turning linting into a quadratic walk over millions of constants on mathlib — `lake lint` effectively never terminated (the bench and mathlib CI lint timeouts). Store the mapping as a `Thunk` on `Kernel.Environment`, filled in `finalizeImport` from the merged views: the runtime forces thunks exactly once, safely across threads (`lean_thunk_get_core` claims the closure atomically and `mark_mt`s the value), so downstream per-declaration reads are constant time again after a single fold, matching the eagerly-built field this accessor replaced. Code that manually reconstructs a `Kernel.Environment` with different views carries the old thunk, which matches how the precomputed field behaved under the same surgery. Verified: `lake lint` on a from-scratch batteries build previously hung indefinitely in `getDeclsInPackage` and now completes. Co-Authored-By: Claude
|
Benchmark results for leanprover-community/mathlib4-nightly-testing@a04b6d1 against leanprover-community/mathlib4-nightly-testing@5edfd4c are in. There are significant results. @Kha
Large changes (1🟥)
Small changes (3978🟥)
|
The extra-constants view (codegen names consulted only when a name misses the constants view) was merged eagerly at import like the constants view, although its duplicate report is not consumed. Merge it lazily instead: a lazy node holds the candidate subtrees sharing its prefix and computes its children on first descent via a thunk, one level at a time; entry lookups at the prefix itself answer directly from the candidates. Thunk forcing is thread-safe and supported on persistent objects, so lazy nodes are safe to share across elaboration threads, and the process-global lookup cache keeps each forced walk to once per name. This removes the extras merge (over half the total merge work) from the import path: `import Mathlib` drops 7% task-clock locally, and even extras-heavy elaboration workloads come out slightly ahead since only touched prefixes are ever merged. Also ports the leanchecker `ReplaceAxiom` helper to the `modifyAt`/`mkMerged` API, which the new constructor's exhaustiveness requirement would otherwise break. Co-Authored-By: Claude
|
!bench |
|
Benchmark results for de7e9f4 against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (51✅, 21🟥)
Medium changes (33✅, 11🟥)
Small changes (27✅, 2476🟥)
|
|
!bench mathlib |
|
Benchmark results for leanprover-community/mathlib4-nightly-testing@5843b6f against leanprover-community/mathlib4-nightly-testing@5edfd4c are in. There are significant results. @Kha
Large changes (2🟥)
Small changes (68🟥)
|
|
!bench |
|
Benchmark results for 6f3486c against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (50✅, 22🟥)
Medium changes (4✅, 44🟥)
Small changes (15✅, 2472🟥)
|
The imported-constant cache marked its values multi-threaded so reader threads could reference them atomically, which makes the reference count of hot values (`Eq`, `Nat`, common instances) a cache line bouncing between cores on every lookup from every elaboration thread. Add `lean_mark_persistent_unshared`, a `lean_mark_persistent` variant that skips multi-threaded objects and their subgraphs: it only writes thread-owned reference counts, so it is safe where the plain variant races, and the skipped fringe stays alive below persistent parents since `dec` never traverses persistent objects. Marking cache values with it makes hits free of atomic reference counting and of coherence traffic: 1-2% fewer cycles on elaboration workloads and 3% less CPU time on parallel-elaboration-heavy ones, at unchanged instruction counts. Co-Authored-By: Claude
|
!bench |
|
Benchmark results for 5460173 against 8006bb0 are in. There are significant results. @Kha Warning These warnings may indicate that the benchmark results are not directly comparable, for example due to changes in the runner configuration or hardware.
Large changes (51✅, 22🟥)
Medium changes (30✅, 10🟥)
Small changes (30✅, 2475🟥)
|
This PR replaces the eager construction of the imported-constants hash maps in
finalizeImport(privateConstantMap,publicConstantMap,const2ModIdx; ~530k insertions forimport Lean) with prefix trees over each module's constant names that are precomputed at olean write time and remain in the memory-mapped compacted regions. Import now only merges the per-module trees, allocating fresh nodes solely for name prefixes shared between modules (~3k nodes / ~81k edges for the stdlib), and lookups walk the merged view, disambiguating children by cached per-prefixNamehash.import Leandrops from ~527ms to ~393ms; olean sizes grow by ~6%.Details:
Lean.ConstTrie: generic per-module tree (ConstTrie α), merged import view (ImportedConsts α) with single-module subtrees borrowed from the regions and module-index tagging, and the newConstMapstructure (importedview +localsmap).ModuleDatagainsconstTrie/extraConstTriefields, filled centrally insaveModuleData(Parts). The extra-const tree replaces the 325k codegen-name entries ofconst2ModIdx(Kernel.Environmentnow holdsallImportedConsts/importedExtraConstsinstead, servinggetModuleIdxFor?).subsumesInforealizability subsumption andthrowAlreadyImported) from the merge's reported collisions.saveModuleDataParts, all parts' data must stay alive across the chained compactor saves; the tries are therefore attached to all parts up front (the compactor tracks compacted objects by heap address, so per-part temporaries would corrupt later parts via address reuse).stage0/src/stdlib_flags.htemporarily enablesinterpreter.prefer_nativeas this is an ABI-breaking change toKernel.Environment/ConstMap(to be reverted byupdate-stage0). Stage 1 tests are meaningless on this commit since the stage 1 oleans lack the newModuleDatafields; validate against stage 2.