JOURNAL: Iter 18f.2 — tail-latency bench, latency-axis SUPPORTED
Bencher ran the tail-latency bench post-RC-fix. Boehm's p99=7131µs is 74× its median (96µs); explicit-mode RC's p99=453µs is 1.62× its median (280µs). Decision 10's real-time claim has a first piece of evidence on the correct axis (tail latency) — 18f's throughput bench measured the wrong thing. Surprise finding: explicit-mode RC leaks at the same RSS rate as implicit-mode RC (511MB peak on both). The (own ...) + (reuse-as) + (drop-iterative) annotations don't drive deallocation in tail-recursive arms because pattern-binder t is consumed into the tail-call (consume_count > 0, Iter A skips); the outer LCons husk has all its ptr fields moved out but no drop site shallow-decs it. Genuine 18d.4 implementation gap, queued as Iter 18g (#82). Retirement decision (Path A vs Path B from 18f) still open; now joined by the outer-cell-shallow-dec finding. Both feed the eventual decision.
This commit is contained in:
@@ -7728,3 +7728,98 @@ arm-close pattern-binders don't dec what they shouldn't; it
|
||||
does not start dec'ing what they should. Net effect on
|
||||
Implicit fixtures: same correctness as `--alloc=gc` (no
|
||||
regression), allocate-path tax only (matches the 18f bench).
|
||||
|
||||
## 2026-05-08 — Iter 18f.2: tail-latency bench, Decision 10 supported on the latency axis
|
||||
|
||||
`ailang-bencher` ran the tail-latency bench that the 18f
|
||||
throughput bench couldn't speak to. New harness:
|
||||
`bench/latency_harness.py` — PTY-line-buffered stdout +
|
||||
`monotonic_ns` per line + inter-arrival distribution. Paired
|
||||
fixtures: `bench_latency_{implicit,explicit}.ailx`.
|
||||
|
||||
Hypothesis: under sustained alloc pressure with a large
|
||||
persistent live working set (depth-19 tree, ~16 MB), Boehm has
|
||||
a long STW tail (p99 ≫ median); explicit-mode RC has p99 within
|
||||
a small constant factor of median.
|
||||
|
||||
### Numbers (5 runs, AMD 5900X)
|
||||
|
||||
```
|
||||
arm | wall(s) | RSS(MB) | median | p99 | p99.9 | max | p99/med
|
||||
implicit-mode @ Boehm GC | 0.238 | 66 | 96.2 | 7131 | 7987 | 7989 | 74.1×
|
||||
explicit-mode @ RC | 0.348 | 511 | 280.3 | 453 | 521 | 526 | 1.62×
|
||||
implicit-mode @ RC (control) | 0.340 | 511 | 278.5 | 450 | 456 | 492 | 1.61×
|
||||
```
|
||||
|
||||
Units µs unless stated.
|
||||
|
||||
### Latency-axis verdict: SUPPORTED
|
||||
|
||||
Boehm's max (~7.99 ms) is **83× its median**. RC's max is
|
||||
**1.85× median**. Boehm's STW pauses are real and ~7 ms wide.
|
||||
RC has no comparable tail. The signal sits two orders of
|
||||
magnitude above the harness noise floor (~27 µs printf+pipe
|
||||
roundtrip), so it's not an artefact.
|
||||
|
||||
This is the first piece of evidence for Decision 10's
|
||||
real-time claim. Boehm-retirement is *not* unblocked yet — see
|
||||
the new finding below — but the latency axis, which 18f
|
||||
admitted it didn't measure, now has signal.
|
||||
|
||||
### Surprise finding: explicit-mode RC leaks at the same rate as implicit-mode RC
|
||||
|
||||
Both RC arms peak at 511 MB RSS. Implicit-mode RC leaking is
|
||||
expected (18c.3 documented debt). Explicit-mode RC leaking the
|
||||
same amount is **not** what Decision 10 + the 18-arc promised.
|
||||
The mode annotations + `(reuse-as)` + `(drop-iterative)` are
|
||||
all present in `bench_latency_explicit.ailx`'s hot path
|
||||
(`sum_list_acc`, `cons_n_acc`), and they should drive prompt
|
||||
deallocation of each per-op IntList.
|
||||
|
||||
Bencher's diagnosis: the hot path is tail-recursive
|
||||
(`tail-app sum_list_acc t ...`); the pattern-binder `t` is
|
||||
*consumed* into the tail-call, so its `consume_count > 0` at
|
||||
arm-close, so 18d.4 Iter A skips. The outer LCons cell
|
||||
(scrutinee `xs`) has all its pointer fields moved out (`h` is
|
||||
Int, `t` was moved into the tail-call), so the outer cell is a
|
||||
husk — but no drop site emits the shallow `ailang_rc_dec` for
|
||||
it. moved_slots correctly records the fields are gone; nothing
|
||||
wires "all fields moved → outer cell can shallow-dec".
|
||||
|
||||
This is a genuine 18d.4 implementation gap, not a bug in the
|
||||
existing emission seams. Iter A and Iter B are correct as
|
||||
specified — they handle the *binder*-level drop. What's
|
||||
missing is the *outer-cell* shallow-dec when the moved-from
|
||||
cell becomes a husk inside a tail-recursive arm.
|
||||
|
||||
### What this implies for the retirement question
|
||||
|
||||
The 18f throughput bench failed its 1.3× target. This bench
|
||||
shows that target was the wrong axis: on the *correct* axis
|
||||
(tail latency), RC wins decisively. But the leak finding means
|
||||
"explicit-mode RC works as designed" is not yet established.
|
||||
Two open questions, both implementer territory:
|
||||
|
||||
1. **Outer-cell shallow-dec for moved-from scrutinees.** The
|
||||
immediate fix the bencher's finding points to. Should
|
||||
close the leak path on the canonical tail-recursive
|
||||
list/tree fixtures.
|
||||
2. **Re-bench after the fix.** Confirm RSS drops to live-set
|
||||
+ small constant on the explicit arm. Median latency may
|
||||
tick down (less alloc pressure → fewer libc-malloc calls).
|
||||
Tail latency on the explicit arm should tighten further.
|
||||
|
||||
After both land, the retirement decision (Path A vs Path B
|
||||
from 18f's entry) gets a real evidence base.
|
||||
|
||||
### What this iter (18f.2) ships
|
||||
|
||||
- `bench/latency_harness.py` (committed in `ac70011`).
|
||||
- `examples/bench_latency_{implicit,explicit}.{ailx,ail.json}`.
|
||||
- This entry. No DESIGN.md change yet — the leak is an impl
|
||||
gap, not a Decision 10 revision.
|
||||
- A new task (`#82` outer-cell shallow-dec) is queued.
|
||||
|
||||
The 18f throughput entry's "Path A vs Path B" framing remains
|
||||
open and is now joined by the outer-cell-shallow-dec finding;
|
||||
both feed the eventual retirement decision.
|
||||
|
||||
Reference in New Issue
Block a user