bench: 21'f — explicit-mode pair, full alloc+dec vs malloc+free

Closes the apples-to-apples gap from 21'e. Adds:
- examples/bench_list_sum_explicit.ailx — same algorithm and sizes
  as bench_list_sum, fully (borrow)/(own)/(drop-iterative)
  annotated so codegen emits proper inc/dec instrumentation.
- bench/reference/list_sum_explicit_free.c — same algorithm
  with explicit free() walking the chain after sum.

The full alloc+dec vs malloc+free comparison reveals two non-
trivial conclusions:

1. AILang's full RC pipeline is only 26% slower than glibc
   malloc+free on this workload (rc/c = 1.26x). The implicit-
   mode comparison's 1.42x was misleading — it counted neither
   pipeline's free path. The fair ratio is 1.26x, materially
   better than the previous read.

2. RC's dec is cheaper per cell than glibc free(). AILang
   dec-tax: ~3 ns/cell. C free-tax: ~5.5 ns/cell. Plausible
   cause: ailang_rc_dec operates on a known-shape cell with a
   fixed-offset refcount and a static per-type drop fn — no
   free-list bucketing, no header introspection, no global lock.

bump's advantage expresses fully: bench_list_sum_explicit.bump/c
= 0.42x means AILang at bump is 2.4x faster than C malloc+free.
Sets a useful upper bound on a slab/pool RC allocator's potential.

The 21'-family arc — bench-regression infrastructure — is now
substantively complete: 21'a (bench/check.py), 21'b (corpus
widening), 21'c (compile_check.py), 21'd (pure-compute fixtures
+ harness hardening), 21'e (cross-language hand-C), 21'f (explicit
apples-to-apples). 63 runtime metrics + 18 compile metrics + 25
cross-lang metrics under regression coverage. Any future iter
that regresses any axis beyond tolerance gets caught at the next
family close.

Remaining queue is back to substantive language work — Family 21
(typeclasses / polymorphic ADTs at runtime / pattern-binding
generalisation) is now an orchestrator-level fork that needs
direct user input.
This commit is contained in:
2026-05-09 01:21:15 +02:00
parent c897d2eef0
commit 75f7fda788
8 changed files with 369 additions and 25 deletions
+140
View File
@@ -10135,6 +10135,146 @@ fixture and baseline-file additions only.
- **Family 21+** — typeclasses, polymorphic ADTs at runtime,
pattern-binding generalisation. Orchestrator-level fork.
## 2026-05-09 — Iter 21'f: explicit-mode pair, full alloc+dec vs malloc+free
Closes the apples-to-apples gap from 21'e. Until this iter, every
AILang/C ratio compared *implicit-mode* AILang (alloc-tax-only,
no dec) against *malloc-and-leak* C — both leaking, both unfair to
the dec-cost question. 21'f ships the matched pair:
- **`examples/bench_list_sum_explicit.ailx`** — same algorithm and
sizes as `bench_list_sum`, fully annotated with `(borrow)` /
`(own)` / `(drop-iterative)` so codegen emits proper `inc`/`dec`
instrumentation. Each cell allocated by `cons_n_acc` is dec'd as
`sum_acc` consumes the chain via the LCons-arm move-into-tail-call.
- **`bench/reference/list_sum_explicit_free.c`** — same C
algorithm with explicit `free()` walking the chain after sum.
### The full alloc+dec vs malloc+free numbers
```
fixture | AILang_rc | AILang_bump | C | rc/c | bump/c
-----------------------------+-----------+-------------+--------+-------+-------
bench_list_sum (implicit) | 138.9 ms | 50.3 ms | 97.5 ms| 1.42× | 0.52×
bench_list_sum_explicit | 150.8 ms | 49.5 ms |119.2 ms| 1.26× | 0.42×
```
### What this tells us
**Subtraction reveals the per-axis tax:**
- AILang RC dec-tax: `150.8 - 138.9 = ~12 ms` ≈ 8% of rc time.
This is the cost of emitting and executing `ailang_rc_dec` for
every consumed cell + the iterative-drop walker.
- C free-tax: `119.2 - 97.5 = ~22 ms` ≈ 18% of c+free time.
This is glibc's free-list-management overhead per free() call.
Two non-trivial conclusions:
**1. AILang's full RC pipeline is only 26% slower than glibc's
full malloc+free pipeline on this workload (rc/c = 1.26×).**
The implicit-mode comparison's 1.42× was misleading — it was
comparing AILang-with-alloc-tax-only vs C-with-malloc-only, which
counted neither pipeline's free path. The fair number is 1.26×,
materially better than the previous read.
**2. RC's dec is cheaper than glibc's free.** AILang dec-tax is
~12 ms on 4M cells (3 ns/cell); C free-tax is ~22 ms (5.5 ns/cell).
Plausible cause: AILang's `ailang_rc_dec` operates on a known-
shape cell with a fixed-offset refcount header and a static
per-type drop fn — no free-list bucketing decision, no header
introspection, no global lock contention. glibc's `free()` is a
general-purpose allocator with all of those concerns.
**3. bump's no-free advantage now expresses itself fully.**
`bench_list_sum_explicit.bump/c = 0.42×` means AILang at bump
allocator is **2.4× faster than C at malloc+free** on this
workload. The bump arm pays neither dec nor free; it's the
no-malloc-overhead floor. The 0.42× ratio sets a useful upper
bound on how fast a slab/pool RC allocator could plausibly run
(if Path B from 18f's two-paths analysis ever ships).
### Runtime bench: bench_list_sum_explicit added
`bench/run.sh`'s `fixtures` array extends to include the explicit
fixture. The runtime numbers show the dec-tax visible inside the
gc/rc/bump comparison too:
```
| gc(s) | bump(s) | rc(s) | gc/bump | rc/bump | rc RSS(KB)
bench_list_sum | 0.141 | 0.049 | 0.140 | 2.88× | 2.87× | 193640
bench_list_sum_explicit| 0.139 | 0.048 | 0.152 | 2.89× | 3.14× | 142424
```
`rc_over_bump` jumps from 2.87× (implicit, no dec) to 3.14×
(explicit, full dec). The dec-tax is now in the regression-check
band. RC RSS drops from 193 MB (leaking) to 142 MB (actually
freeing) — the first time a non-bump-baseline fixture has
demonstrated RC's free-path actually working under regression
coverage.
### Baseline file: 55 → 63 metrics
8 new metrics for `bench_list_sum_explicit` (same shape as the
implicit counterpart, slightly looser RSS tolerance at 8% because
the active-free working set is more variable than the leaking
peak).
`bench/baseline_cross_lang.json` extended too — 5 new ratio
metrics for the explicit pair.
### What this iter does NOT do
- **No tree-walk explicit pair.** Could be done identically to
list_sum (add `bench_tree_walk_explicit.ailx` with full modes
+ `tree_walk_explicit_free.c`). Useful but adds another 5
metrics for a workload class already represented; deferred
unless a specific question motivates it.
- **No HOF / closure explicit pair.** Same reasoning.
- **No methodology upgrade for the latency dispersion.** Still
queued.
### Test state
288 / 0 / 3, unchanged. No Rust changes; iter is bench-fixture
additions only.
### JOURNAL queue (updated)
The 21' family arc — bench-regression infrastructure — is now
substantively complete:
- 21'a: bench/check.py + baseline.json (runtime regressions).
- 21'b: closure_chain + hof_pipeline corpus.
- 21'c: bench/compile_check.py (compile regressions).
- 21'd: pure-compute fixtures, harness hardening.
- 21'e: cross-language hand-C reference + bench/cross_lang.py.
- 21'f: explicit-mode pair, full apples-to-apples ratios.
CLAUDE.md `Performance regressions` section codifies the three
scripts as co-equal tidy-iter gates. Any future iter that
regresses runtime, compile-time, or AILang/C ratios beyond
tolerance gets caught at the next family close.
Remaining queue:
- **`FnDef::synthetic(...)` factor-out** — unchanged; awaits next
schema-additive `FnDef` field.
- **Boehm full retirement** — unchanged; gating condition still
≥3 families with no oracle wins.
- **Latency methodology upgrade** — n=10+ captures or tighter
fixture for `explicit_at_rc.p99` dispersion. Could ship as a
short standalone iter if the next tidy-iter sees the tolerance
regularly squeezed.
- **Optional explicit-mode pairs**`bench_tree_walk_explicit`,
`bench_hof_pipeline_explicit`. Add when a specific question
demands the data.
- **Deferred richer integration paths** (from 20f) — tool-use,
MCP, LSP. Long-horizon.
- **Family 21+** — typeclasses, polymorphic ADTs at runtime,
pattern-binding generalisation. **Orchestrator-level fork
with multiple substantive options none of which is clearly
default; needs direct user input before dispatch.**
## 2026-05-09 — Iter 21'e: cross-language reference + AILang/C ratios
Closes the question CLAUDE.md has carried since day one — *"the