Prototype: spill Buf and HeapBuf into private regions#103
Conversation
|
Full-build benchmark (gcc example, 3529 commands from scratch, putup-only counters via
The RSS comparison is swamped by an unrelated discovery the benchmark surfaced: putup's build-time peak RSS is ~1 GB, of which ~845 MB is a single arena mapping produced by 🤖 Generated with Claude Code |
3a7ece7 to
5561828
Compare
|
Re-benchmark on top of the arena fix (#104 merged; branch rebased onto main c9bde20, 520 cases green). With the ~1 GB arena noise gone, allocator-level deltas are now measurable:
(Instructions +0.6% on builds, +2.3–2.7% on parse/dry-run, deterministic. The spill full-build wall clock was polluted by concurrent local work — cycles are the trustworthy column there.) Updated verdict: the picture sharpens but doesn't change — ~2% cycles on parse-shaped workloads and neutral on builds buys the structural no-strand guarantee and −21.7 MB of deterministic gross churn, with ≤1 MB of realized RSS on today's workloads. This is now purely a judgment call on whether the insurance is worth the small constant cost. 🤖 Generated with Claude Code |
|
Re-benchmark on top of the arena fix (#104, now on main; branch rebased onto
(Side note: parse peak RSS dropped 46.5 → 18.3 MB from #104 alone — the parse-phase DAG construction hit the same quadratic.) Updated verdict: the RSS win is real but small (0.4–0.9 MB, ~1–2.5%) at a cost of ~+2% cycles on graph-heavy operations (neutral on full builds, where fork/hash dominates). The deterministic effects — 21.7 MB of gross churn off the bump, and the structural guarantee that spills can never strand regardless of allocation interleaving — are unchanged. This is now purely a robustness-vs-constant-cost judgment call, with honest numbers on both sides. 🤖 Generated with Claude Code |
A spilled Buf's lifetime routinely brackets other bump allocations (interleaved Vec growth, nested Bufs, operator new), so its blocks can neither extend in place nor be reclaimed at destruction; HeapBuf file contents and process output strand structurally, pinned under everything their consumer allocates. Owning the spill as a per-buffer vm reservation makes growth in-place and reclamation unconditional: the region is released whole when the buffer dies. Reservations start at 16 MiB and are replaced with a doubled reservation on exhaustion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5KUoGDhJEZYoiK9CkeXtz
5561828 to
0d35a52
Compare
PR metricsPerformance (gcc example, Linux)
Deterministic signals: page faults, peak RSS, and the cachegrind D1/LL miss rates (simulated cache, exact across runs). CPU time is user+sys from time(1); instructions read n/a on GitHub-hosted runners (virtualized, no PMU). Test coverage (lines)
93 files · 13697/16104 lines covered Deltas vs main@052d1a8bd. Updated for 2804c30 |
|
Follow-up pushed: HeapBuf is deleted (c6c992b, −418/+96). The audit behind it: HeapBuf's move-by-value was load-bearing at exactly one site ( Changes:
Verification: Linux suite 62,523 assertions in 512 cases green; Windows cross-build clean and 60,953 assertions in 287 cases green under Wine ( |
HeapBuf duplicated Buf's whole API for one load-bearing difference: being returnable by value from read_file. Every other use site (process capture, JobSlot accumulators, win32 conversion scratch) accumulates, interns, and discards - exactly Buf's job, and Buf's 4 KiB inline buffer now absorbs the common small case instead of paying a region round-trip per call. read_file flips to the output-parameter convention (return value = immutable, output param = mutable): read_file(path, Buf&) -> Result<void>. Buf gains resize() so the read loop can write straight into it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5KUoGDhJEZYoiK9CkeXtz
1d8b0fb to
2804c30
Compare
What
Replaces the bump-region spill path in
BufandHeapBufwith a privateRegionowned by each buffer (container-as-allocator taken to its conclusion for the string builders):Buf::growpast the 4096-byte inline buffer now commits into an owned vm reservation (16 MiB initial, replaced with a doubled reservation on exhaustion — no abort at any size).HeapBufbecomes a thin owner of aRegionplus a size; its backing store is the region directly.Regiongains areserved()accessor to support the replacement-growth path.bump_try_extend/bump_try_pop) disappear from both types; the destructor releases the whole reservation unconditionally.Why
A case analysis of all ~137 Buf/HeapBuf sites showed the buffers that actually spill (command/expression expansion,
read_filecontents, process-output accumulators) are exactly the ones whose lifetime brackets other bump allocations — so LIFO reclaim structurally cannot fire for them, and every regrowth after an interleaved allocation orphans a block. Owning the spill as a region makes growth in-place (base never moves) and reclamation unconditional, as a structural property rather than a lifetime coincidence.Measurements (gcc-example parse, 1852 commands, perf -r 5)
Honest read: the parse benchmark moves 7.1 MB of gross churn off the bump but recovers only ~0.5 MB RSS — on this workload the obstack was already reclaiming ~95% of spill bytes, and parse never exercises the two structurally-worst cases (JobSlot process-output buffers don't run without executing commands;
read_fileonly sees 24 small Tupfiles). The cost is +2% cycles from mmap traffic. The value delivered is the structural guarantee (no spill can strand or churn regardless of interleaving) and full memory return on buffer death — the measured parse-time win is small.Tests
Four new RED-first tests: spills consume zero bump bytes (Buf + HeapBuf), spilled pointer stays stable while the bump top is taken by another allocation, and growth past the 16 MiB reservation preserves contents off-bump. Plus a pin for HeapBuf move semantics (a stale comment claimed it was non-movable). Full suite: 60271 assertions in 525 cases, green.
make iwyuclean; clang-tidy has no diagnostics in the touched production files.🤖 Generated with Claude Code
https://claude.ai/code/session_01U5KUoGDhJEZYoiK9CkeXtz