Carry green query values forward from the previous cache file - #110
Draft
xmakro wants to merge 1 commit into
Draft
Carry green query values forward from the previous cache file#110xmakro wants to merge 1 commit into
xmakro wants to merge 1 commit into
Conversation
Saving the incremental query cache used to require every value it contains to be in memory: values of green nodes that were never used during the session were loaded from the previous cache file by the promotion pass, only to be re-encoded, byte for byte equivalent, into the new file. On a warm rebuild this decode and re-encode round trip for the entire cache dominates the cost of saving. Instead, copy the previous cache file's data region into the new file verbatim and reference the values of green nodes at their old positions. Copying the region unchanged keeps every internal absolute reference valid: type and predicate shorthands, symbol offsets and allocation data all stay at their offsets. The lookup tables around the region are adjusted instead: source file indices preserve all previous assignments and new files append after them, the allocation index keeps the previous entries and offsets this session's, expansions are keyed by their stable hash and merge, and syntax context tables become generational, one per carried region, selected by the position a context id is decoded from, since the raw ids are local to the session that wrote them. After eight carried generations the save falls back to a full re-encode, which compacts away dead data left behind by red nodes and stale tables. Values are now tagged with their node's key fingerprint instead of its session-local index, so the load side sanity check keeps working for values of any age. The promotion pass is deleted: green values no longer need to be in memory at all, and its remaining purpose, referencing them in the next session, is now a walk over the color map that costs a map insert per green node. Values that are in memory anyway, because their query ran this session, are still encoded freshly, and the load path prefers the fresh entry. On Windows the previous file cannot stay mapped while it is replaced, so the carry is disabled there for now and green values that were not loaded during the session are recomputed by the next one, matching what already happened to never-loaded values before this change. The header format version is bumped so caches written by earlier compilers, whose footer layout differs, are discarded cleanly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Carry green query values forward from the previous cache file, rebased onto current main and measured.
Saving the incremental query cache used to require every value it contains to be in memory: values of green nodes that were never used during the session were loaded from the previous cache file by the promotion pass, only to be re-encoded, byte for byte equivalent, into the new file. On a warm rebuild this decode and re-encode round trip for the entire cache dominates the cost of saving. Self-profiling match-stress incr-unchanged shows the shape clearly: cache promotion 9% of self time, incremental value loading 10%, plus the re-encoding on top.
Instead, copy the previous cache file's data region into the new file verbatim and reference the values of green nodes at their old positions. Copying the region unchanged keeps every internal absolute reference valid (type and predicate shorthands, symbol offsets, allocation data); the lookup tables around the region are adjusted instead: source file indices preserve previous assignments, the allocation index offsets this session's entries, expansions merge by stable hash, and syntax context tables become generational, selected by the position a context id is decoded from. After eight carried generations the save falls back to a full re-encode, which compacts away dead data left behind by red nodes. Values are tagged with their node's key fingerprint instead of a session-local index so the load-side sanity check works for values of any age.
With this, values are only decoded when a query actually demands them and are never re-encoded. The promotion pass (
exec_cache_promotions) and the per-query promotion machinery disappear entirely; the remaining bookkeeping is a color-map walk that costs a map insert per green node.Local dist-fidelity A/B (stage2, ThinLTO plus jemalloc, clean from-scratch builds on both sides, instructions:u, full benchmark matrix, mean per cell group):
The biggest cells are the cache-heavy incremental-unchanged benchmarks: ctfe-stress -58%, ucd -45%, tuple-stress -42%, coercions -33%, match-stress -25%; incr-patched has coercions println at -24.7%. Every incr-unchanged cell across all three profiles improves (worst is -0.07%). Full and incr-full builds pay a small bookkeeping cost, worst cell +0.43%, and the worst regression in the whole matrix is +0.83% (an incr-patched cell).
Correctness: tests/incremental passes (180/180), and a five-session chained rebuild of syn (full, unchanged, unchanged, patched, unchanged) works, which exercises values being carried forward across multiple generations of cache files.
Interaction with verification: values that are carried forward are not loaded, so the sampled fingerprint verification only sees values that some query actually demanded. That matches what a real user build executes.
A companion experiment on the branch perf/durability-strata measured the other half of the durability idea, a linear wavefront promotion pass over the previous dep graph: promote every node whose dependencies are already green, in one index-order scan after lowering, instead of discovering green nodes through recursive marking. It is correct (same test suite passes) and covers 60 to 70% of the graph in one pass, but it measures as a small net cost, about +0.6 to +0.9% on incremental scenarios when added on top of this change: the recursive walk it replaces is already demand-driven and linear in the graph, so the scan only removes traversal shape, not work, and pays for an extra pass over the index space. It would become interesting only combined with promotion-encode removal, which is out of scope here, so it is not part of this PR.
Caveats: