From 832c325e9d04105fe3f18bd98ad9298a4e4dc8fe Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 8 May 2026 12:57:51 +0200 Subject: [PATCH] =?UTF-8?q?JOURNAL:=20Iter=2018f=20=E2=80=94=20RC=20bench?= =?UTF-8?q?=20results,=20Boehm=20retirement=20DEFERRED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the bench numbers (rc/bump 2.5–2.9x on the Implicit-mode fixtures, missing Decision 10's 1.3x retirement target) and the two paths forward — lower the threshold (Path A, pragmatic; rc and gc are within 5% of each other so retirement under "RC ≤ Boehm ± 5%" qualifies) vs build a slab/pool allocator first (Path B, larger iter, closes most of the gap to bump). The 1.3x criterion was orchestrator-level commitment in Decision 10 and not meeting it is a prompt for an orchestrator- level discussion, not for the implementer to autonomously flip the default. 18f deliberately stops without flipping default, without dropping -lgc, without marking Decision 9 historical. --- docs/JOURNAL.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index f82fc3b..be6b46a 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -7497,3 +7497,132 @@ the entire 18-arc surface (all of 18a, 18b, 18c.x, 18d.x, 18e shipped under different mental models as I learned the problem; the architect can flag where DESIGN.md and the shipped code have drifted, and decide which side moves). + +## Iter 18f — RC validation bench (Boehm retirement DEFERRED) + +Bench/run.sh extended with an `--alloc=rc` column. Decision +10's retirement criterion was: "RC within 1.3× of bump on +`bench/run.sh`. If met, retire Boehm: flip default to +`--alloc=rc`, drop `-lgc`, mark Decision 9 historical." + +Results on the 2 shipping bench fixtures (RUNS=5, drop slowest, +median of 4): + +``` +workload | gc(s) | bump(s) | rc(s) | gc/bump | rc/bump | gc RSS(KB) | bump RSS(KB) | rc RSS(KB) +-----------------------+---------+----------+----------+---------+---------+------------+--------------+------------ +bench_list_sum | 0.142 | 0.049 | 0.140 | 2.90× | 2.86× | 103788 | 97628 | 193692 +bench_tree_walk | 0.101 | 0.038 | 0.096 | 2.66× | 2.53× | 73452 | 55452 | 109208 +``` + +`rc/bump = 2.5–2.9×`. **Target not met. Boehm retirement +deferred.** + +### Why RC is much slower than bump on these workloads + +The bench fixtures (`bench_list_sum`, `bench_tree_walk`) are +both Implicit-mode — they were authored before the 18a explicit- +mode infrastructure shipped, and they declare no `(borrow T)`, +`(own T)`, `(clone X)`, `(reuse-as ...)`, or `(drop-iterative)` +annotations. Under `--alloc=rc` with Implicit-mode params: + +- `ailang_rc_alloc` is called instead of `bump_malloc`. That + alone is a step from a hot-path bump-pointer (`ptr += size; + return old_ptr`) to a libc `malloc(size + 8)` call plus + header init plus payload memset. Libc malloc is a general- + purpose allocator, not optimised for the fixed-size ADT-cell + pattern AILang exercises hot. +- No `inc`/`dec` is emitted on Implicit-mode params (18c.3's + known debt — `Implicit` is the back-compat opt-out lane). + The cells leak. This means RC's TIME cost is purely the + allocate-path tax — no free-path tax — but the allocate + tax is large. +- Under bump, `bump_malloc` is a 2-instruction inline. Under + RC, `ailang_rc_alloc` is a libc call. The 2.5–2.9× ratio is + in line with this single-factor difference. + +### The relevant comparison: gc/bump ≈ rc/bump + +The numbers also show **gc and rc are within 5% of each other +on both fixtures.** Boehm's `GC_malloc` and `ailang_rc_alloc` +both go through libc-malloc-equivalents and pay similar +per-call costs. RC isn't slower than Boehm in any meaningful +sense — they're tied. + +So flipping the default from Boehm to RC would not regress +performance on Implicit-mode fixtures; it would just preserve +the current cost. The reason to *retire* Boehm rather than +just *make RC default* is: removing the `-lgc` link dependency +and Boehm's runtime overhead simplifies the toolchain. But +that simplification is independent of the 1.3× target. + +### Two paths forward + +**Path A: Lower the retirement threshold.** Decision 10's +1.3× was set in expectation of an inlined slab/pool allocator +in `runtime/rc.c`. A more honest target given the current +implementation is "RC ≤ Boehm ± 5%". On that bar, retirement +DOES qualify. The trade-off is that the language is committed +to RC at performance parity with Boehm rather than at +bump-allocator-floor performance. + +**Path B: Build the slab/pool allocator first.** Replace +`malloc(size + 8)` in `ailang_rc_alloc` with a fixed-size +slab allocator (one slab class per common cell size, free-list +recycling, batched OS allocation). This would close most of +the gap to bump on cell-allocation-heavy workloads. New iter +work, ~one full iter on its own. Then re-run the bench; if +within 1.3×, retire Boehm under the original criterion. + +**Path A is the pragmatic call.** Boehm's main defect was +unbounded GC pause variability and the `-lgc` dependency. Both +go away under either path. Path B's perf win is real but +narrow (helps the alloc-heavy hot path, doesn't help anything +else). Path B is an iter we can do later as a tuning pass when +real workloads show the alloc tax bites. + +### What this iter does NOT do (deliberate) + +- Does NOT flip the default to `--alloc=rc`. The retirement + decision is the orchestrator's call between Path A and + Path B; both are real options that need user input. +- Does NOT drop `-lgc`. Same. +- Does NOT mark Decision 9 historical. Decision 9 still + documents the transitional Boehm state we're in. + +### What it does ship + +- `bench/run.sh` extended with the `rc` column and the + `rc/bump` ratio (the decisive number). +- The bench numbers above, recorded in this entry. +- This entry, which decides the retirement question by + declining to decide it autonomously: the 1.3× criterion was + the orchestrator's commitment, and not meeting it is a + prompt for an orchestrator-level discussion (Path A vs + Path B), not for an implementer to ship a default flip. + +### Status of the 18-arc + +The 18-arc was: 18a (modes), 18b (runtime), 18c.1–4 (clone + +linearity + uniqueness + per-type drop), 18d.1–4 (reuse-as + +move-aware patterns + scope-close drops), 18e (drop-iterative ++ worklist), 18f (bench + retirement decision). + +All shipping infrastructure is now in place. The behaviour +under `--alloc=rc` for Implicit-mode fixtures is correct (same +output as `--alloc=gc`) and approximately as fast as Boehm. +Explicit-mode fixtures additionally benefit from in-place +reuse, iterative drop, and prompt deallocation at scope close; +the RC machinery covers them too. + +The retirement question is open pending Path A vs Path B, and +the new tidy-iter rule (CLAUDE.md) hasn't fired yet because +the family hasn't formally closed. Two paths forward: + +1. Resolve the Path A / Path B question, ship the corresponding + 18f.2, formally close the 18-arc, then do the tidy-iter. +2. Treat 18f's deferred-decision as the close, do the tidy- + iter NOW, then revisit the retirement question with a clean + slate. + +Both reasonable. Orchestrator's call.