Skip to content

Serve dep-graph edge lists in place from the retained file mapping#60

Open
xmakro wants to merge 1 commit into
perf/base-0713from
perf/dep-graph-edges-in-place-upstream
Open

Serve dep-graph edge lists in place from the retained file mapping#60
xmakro wants to merge 1 commit into
perf/base-0713from
perf/dep-graph-edges-in-place-upstream

Conversation

@xmakro

@xmakro xmakro commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Standalone against upstream master (perf/base-0713 = upstream 3659db0), no dependency on the carry/append-only stack. This is the upstream-first wedge: it lands the "previous dep-graph file stays available for the session" infrastructure as a pure simplification + win, which #42/#43/#44/#58/#59 then build on.

Decoding the previous dep graph used to copy every edge byte out of the file into a freshly allocated buffer (edge_list_data), the largest allocation and copy of the load. Instead, retain the file mapping for the session and serve the edge lists directly from it: the on-disk varint encoding was already the in-memory representation, and each node's edge header now records a position in the file instead of a position in the copied buffer. Decoding a record shrinks to reading its fixed header and skipping over the edges.

This also deletes DEP_NODE_PAD and the capacity-estimation heuristic: the fixed-size overread at the end of an edge list stays in bounds because the records are always followed by the per-kind node counts, the session count and the fixed-size tail.

Precedent: retaining an incremental file's mapping for the session and lazily decoding from it is the query result cache's existing lifecycle (OnDiskCache::serialized_data, dropped just before the save in save.rs: "Drop the memory map so that we can remove the file and write to it"). On Windows the mapping must not outlive the load because the save later renames over the mapped file, so the bytes are copied out once — a single copy comparable in total bytes to the per-record copies this replaces. (The OnDiskCache-style drop-before-rename shape was considered instead of the copy, but releasing the backing mid-session would put synchronization on edge_targets_from, the hottest read path of the marking walk.)

Local rustc-perf (primary crates, instructions:u), idle machine, this change alone vs its base commit:

scenario instructions:u max-rss
incr-unchanged -0.66% +6.1%
incr-patched -0.44% +5.3%
full / incr-full +0.01% / +0.01% (control) +0.2% / +0.1%

27 of 60 cells improved by at least 0.25%, 0 regressed, 0 failed benchmarks.

On max-rss: the retained mapping keeps the record headers resident at peak, which the deleted copy discarded after decode. Those pages are clean and file-backed — the kernel reclaims them under pressure with no swap I/O — whereas the buffer this PR deletes was unswappable anonymous heap. Peak-RSS accounting does not credit evictability, so it shows as a regression; effective memory behavior under pressure is better, not worse.

Validation: tests/incremental 178 passed / 0 failed; 12 generations each of incr-unchanged, of alternating real edits, and of alternating edits under -Zincremental-verify-ich: every generation clean.

Decoding the previous dep graph used to copy every edge byte out of the
file into a freshly allocated buffer (edge_list_data), the largest
allocation and copy of the load. Instead, retain the file mapping for the
session and serve the edge lists directly from it: the on-disk varint
encoding was already the in-memory representation, and each node's edge
header now records a position in the file instead of a position in the
copied buffer. Decoding a record shrinks to reading its fixed header and
skipping over the edges.

This also removes the DEP_NODE_PAD padding and the capacity-estimation
heuristic: the fixed-size overread at the end of an edge list stays in
bounds because the records are always followed by the per-kind node
counts, the session count and the fixed-size tail.

Retaining an incremental file's mapping for the session and lazily
decoding from it is the same lifecycle the query result cache already
uses (OnDiskCache::serialized_data). It also moves the edge bytes from
anonymous heap memory to evictable file-backed pages. On Windows the
mapping must not outlive the load, because the save later renames over
the mapped file; there the bytes are copied out once, a single copy
comparable to the per-record copies this replaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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