Skip to content

perf(runtime): per-thread route-pin cache for foreign frees (B4 R1) - #79

Merged
PHPCraftdream merged 8 commits into
mainfrom
b4-r1-pin-cache
Oct 20, 2026
Merged

PHPCraftdream merged 8 commits into
mainfrom
b4-r1-pin-cache

Conversation

@PHPCraftdream

Copy link
Copy Markdown
Owner

Per-thread cache of route pins for cross-thread (foreign) small frees, so repeated foreign frees into the same segment skip the global RouteDirectory shard-lock lookup.

What changes

  • src/registry/heap_core_xthread/route_pin_cache.rs (new): 4-slot TLS cache keyed by segment base + incarnation. publish_cached is the unsafe transfer-once boundary. Unlinked pins are swept on miss/insert and released at thread exit. Under cfg(all(loom, test)) the cache uses loom::thread_local!.
  • src/registry/segment_route/directory.rs: Entry::linked, stored false with Release under the shard lock on unlink. A cached pin never publishes after unlink.
  • src/registry/heap_core_xthread/routing.rs: cached fast path, uncached slow path, shared tail for the FOREIGN_OR_UNROUTABLE_FREES / B4_FOREIGN_PUBLISH_OK counters and the Miri publication gate.

Evidence

  • Local Windows A/B on top of the class cursor (before rebase): MT +7…13 %, 0/30 collapses.
  • Local, after rebase onto 3f0eab59: B4/route tests pass on 3 feature sets × debug/release; clippy -D warnings is clean on all 8 CI rows; structural tests pass.
  • Loom tests/loom_b4_r1_pin_cache.rs: baseline 4/4. The linked-gate mutant goes red on O2 (1 ≠ 0). The drop-at-exit mutant goes red on O4 (3 ≠ 2).
  • Miri SB/TB (tests/b4_r1_pin_cache_miri.rs) runs on this PR's CI only. It cannot run on Windows (GetModuleHandleW).
  • iai A/B: .github/gate-run.json, removed before merge.

Costs

  • Idle retention: up to ≈1.16 MiB per thread (route descriptors/sidecars held by cached pins until eviction or thread exit).
  • B4_FOREIGN_PUBLISH_OK (bench-internals diagnostic) now also counts a synchronous fallback-heap free.

Report: docs/perf/B4_R1_PIN_CACHE.md.

🤖 Generated with Claude Code

@PHPCraftdream PHPCraftdream added the gate-run Run the r20 gate runner from .github/gate-run.json as a PR check label Oct 19, 2026
PHPCraftdream and others added 2 commits October 19, 2026 13:00
A foreign (cross-thread) small free resolved its segment route through
the global RouteDirectory shard lock on every call. R1 keeps a small
per-thread cache of RoutePin handles keyed by segment base and
incarnation, so repeated foreign frees into the same segment publish
through the already-pinned sidecar without the directory lookup.

- route_pin_cache.rs: 4-slot TLS cache; publish_cached is the unsafe
  transfer-once boundary; unlinked pins are swept on miss/insert and
  released at thread exit; loom::thread_local! seam under cfg(loom, test).
- directory.rs: Entry gains `linked`, stored false (Release) on unlink
  under the shard lock, so a cached pin never publishes after unlink.
- routing.rs: cached fast path, uncached slow path, shared tail counters;
  publish_foreign stays #[cold] so the own-thread free is unchanged.
- Tests: b4_r1_pin_cache (hit/insert/release, re-registration, retention,
  thread exit, totals64), b4_r1_pin_cache_miri (SB/TB, CI only),
  loom_b4_r1_pin_cache (O2 no publish after unlink, O4 release on exit;
  linked-gate and drop mutants go red on the assert oracles).

iai A/B run 2 (39222595039, main 3f0eab5 vs 811bfba):
r19_prf01_joined_producer_same_word -969909 / 1445928 = -67.08 %; own
free cells +1 Ir per cell. callgrind T=1: larson 319 = 319, mstress
494 = 494 Ir/op. See docs/perf/B4_R1_PIN_CACHE.md §7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e merge)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0 PASS, run 39241457236)

Gate configs removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PHPCraftdream PHPCraftdream removed the gate-run Run the r20 gate runner from .github/gate-run.json as a PR check label Oct 19, 2026
PHPCraftdream and others added 5 commits October 19, 2026 15:59
…t Box

The owner-side drain loop dropped a fresh Box<Block> per cycle. On the
non-fastbin miri rows that own free reaches dealloc_small, which writes
the freelist link while the by-value Box::drop frame is live: the accepted
P1-box defect (card 164), not the pin-cache behaviour this test pins. CI
run 39264902142 went red there (miri-plain and miri (core), UB in
Node::write_next from dealloc_small at the loop's drop). The loop now
cycles through GlobalAlloc::alloc/dealloc directly, which keeps the same
own alloc/free drain trigger without a protected Box argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the non-fastbin miri rows the scenario itself passed, then libtest's
teardown dropped a by-value Box on the main thread through the installed
allocator, which hits the accepted P1-box defect (card 164: dealloc_small
writes the link while the Box frame is live). The cell now follows the
miri_global_box_acceptance pattern: harness = false, test = false, one
scenario in main, and a COMPLETE marker printed after the last assertion;
CI and the miri matrix grep that marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	.github/workflows/ci.yml
#	Cargo.toml
#	README.md
#	docs/ARCHITECTURE.md
#	docs/evidence/REGISTRY.md
# Conflicts:
#	README.md
#	docs/ARCHITECTURE.md
#	docs/evidence/REGISTRY.md
@PHPCraftdream
PHPCraftdream merged commit 2730d02 into main Oct 20, 2026
70 checks passed
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