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:
@@ -54,6 +54,16 @@
|
||||
"gc_rss_kb": { "baseline": 13624, "tolerance_pct": 15 },
|
||||
"bump_rss_kb": { "baseline": 13860, "tolerance_pct": 15 },
|
||||
"rc_rss_kb": { "baseline": 13880, "tolerance_pct": 15 }
|
||||
},
|
||||
"bench_list_sum_explicit": {
|
||||
"gc_s": { "baseline": 0.139, "tolerance_pct": 10 },
|
||||
"bump_s": { "baseline": 0.048, "tolerance_pct": 10 },
|
||||
"rc_s": { "baseline": 0.152, "tolerance_pct": 10 },
|
||||
"gc_over_bump": { "baseline": 2.89, "tolerance_pct": 8 },
|
||||
"rc_over_bump": { "baseline": 3.14, "tolerance_pct": 8 },
|
||||
"gc_rss_kb": { "baseline": 103772, "tolerance_pct": 5 },
|
||||
"bump_rss_kb": { "baseline": 97636, "tolerance_pct": 5 },
|
||||
"rc_rss_kb": { "baseline": 142424, "tolerance_pct": 8 }
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -6,89 +6,111 @@
|
||||
"fixtures": {
|
||||
"bench_list_sum": {
|
||||
"ail_rc_s": {
|
||||
"baseline": 0.141597,
|
||||
"baseline": 0.138906,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"ail_bump_s": {
|
||||
"baseline": 0.048038,
|
||||
"baseline": 0.050245,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"c_s": {
|
||||
"baseline": 0.095312,
|
||||
"baseline": 0.09749,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"rc_over_c": {
|
||||
"baseline": 1.485614,
|
||||
"baseline": 1.42483,
|
||||
"tolerance_pct": 12
|
||||
},
|
||||
"bump_over_c": {
|
||||
"baseline": 0.504003,
|
||||
"baseline": 0.515393,
|
||||
"tolerance_pct": 12
|
||||
}
|
||||
},
|
||||
"bench_tree_walk": {
|
||||
"ail_rc_s": {
|
||||
"baseline": 0.097028,
|
||||
"baseline": 0.100173,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"ail_bump_s": {
|
||||
"baseline": 0.038905,
|
||||
"baseline": 0.038673,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"c_s": {
|
||||
"baseline": 0.037159,
|
||||
"baseline": 0.036567,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"rc_over_c": {
|
||||
"baseline": 2.611185,
|
||||
"baseline": 2.739449,
|
||||
"tolerance_pct": 12
|
||||
},
|
||||
"bump_over_c": {
|
||||
"baseline": 1.046998,
|
||||
"baseline": 1.057606,
|
||||
"tolerance_pct": 12
|
||||
}
|
||||
},
|
||||
"bench_compute_intsum": {
|
||||
"ail_rc_s": {
|
||||
"baseline": 0.00044,
|
||||
"baseline": 0.000362,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"ail_bump_s": {
|
||||
"baseline": 0.000393,
|
||||
"baseline": 0.000371,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"c_s": {
|
||||
"baseline": 0.000374,
|
||||
"baseline": 0.000361,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"rc_over_c": {
|
||||
"baseline": 1.177715,
|
||||
"baseline": 1.002676,
|
||||
"tolerance_pct": 12
|
||||
},
|
||||
"bump_over_c": {
|
||||
"baseline": 1.050856,
|
||||
"baseline": 1.028255,
|
||||
"tolerance_pct": 12
|
||||
}
|
||||
},
|
||||
"bench_compute_collatz": {
|
||||
"ail_rc_s": {
|
||||
"baseline": 0.056878,
|
||||
"baseline": 0.057994,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"ail_bump_s": {
|
||||
"baseline": 0.056547,
|
||||
"baseline": 0.057159,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"c_s": {
|
||||
"baseline": 0.05748,
|
||||
"baseline": 0.05776,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"rc_over_c": {
|
||||
"baseline": 0.989523,
|
||||
"baseline": 1.00405,
|
||||
"tolerance_pct": 12
|
||||
},
|
||||
"bump_over_c": {
|
||||
"baseline": 0.983756,
|
||||
"baseline": 0.989593,
|
||||
"tolerance_pct": 12
|
||||
}
|
||||
},
|
||||
"bench_list_sum_explicit": {
|
||||
"ail_rc_s": {
|
||||
"baseline": 0.150754,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"ail_bump_s": {
|
||||
"baseline": 0.0495,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"c_s": {
|
||||
"baseline": 0.119204,
|
||||
"tolerance_pct": 15
|
||||
},
|
||||
"rc_over_c": {
|
||||
"baseline": 1.264667,
|
||||
"tolerance_pct": 12
|
||||
},
|
||||
"bump_over_c": {
|
||||
"baseline": 0.415253,
|
||||
"tolerance_pct": 12
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -47,10 +47,11 @@ OUTDIR = ROOT / "target" / "cross_lang"
|
||||
# (ailang fixture stem, C reference stem) — both must exist as
|
||||
# `examples/<ail>.ail.json` and `bench/reference/<c>.c`.
|
||||
CORPUS = [
|
||||
("bench_list_sum", "list_sum"),
|
||||
("bench_tree_walk", "tree_walk"),
|
||||
("bench_compute_intsum", "compute_intsum"),
|
||||
("bench_compute_collatz", "compute_collatz"),
|
||||
("bench_list_sum", "list_sum"),
|
||||
("bench_tree_walk", "tree_walk"),
|
||||
("bench_compute_intsum", "compute_intsum"),
|
||||
("bench_compute_collatz", "compute_collatz"),
|
||||
("bench_list_sum_explicit", "list_sum_explicit_free"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Hand-C reference for bench_list_sum_explicit — full malloc + free.
|
||||
//
|
||||
// Companion to list_sum.c (which leaks); this version adds explicit
|
||||
// `free()` calls to mirror AILang's explicit-mode RC dec-tax. Pairs
|
||||
// with examples/bench_list_sum_explicit.ailx via bench/cross_lang.py.
|
||||
//
|
||||
// The free pass walks the list after sum, freeing each cell. This is
|
||||
// genuinely O(N) work that bench_list_sum.c never paid; the rc/c
|
||||
// ratio for this fixture is therefore the honest answer to "how
|
||||
// much does RC's dec-and-free pipeline cost vs hand-C's malloc-free
|
||||
// pipeline on the same workload?".
|
||||
//
|
||||
// Note: we sum first, then free, rather than fold-with-free. This
|
||||
// matches the AILang fixture's structure: sum_list owns the chain
|
||||
// and consumes it; the dec walk happens implicitly as sum_acc
|
||||
// pattern-matches LCons and re-binds the tail to the recursive call.
|
||||
// AILang's pattern there is "consume during sum" — codegen emits
|
||||
// dec on the consumed cells as the match arm closes. The C version
|
||||
// could in principle interleave free into the sum loop too; doing it
|
||||
// in two passes is structurally cleaner and the cost is the same
|
||||
// (one O(N) walk + one O(N) walk-and-free).
|
||||
//
|
||||
// Build: clang -O2 -o list_sum_explicit_free list_sum_explicit_free.c
|
||||
// Expected stdout (one int per line):
|
||||
// 4999950000
|
||||
// 499999500000
|
||||
// 4499998500000
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct cell {
|
||||
long head;
|
||||
struct cell *tail;
|
||||
} cell_t;
|
||||
|
||||
static cell_t *cons_n(long n) {
|
||||
cell_t *acc = NULL;
|
||||
for (long i = n - 1; i >= 0; i--) {
|
||||
cell_t *c = (cell_t *) malloc(sizeof(cell_t));
|
||||
c->head = i;
|
||||
c->tail = acc;
|
||||
acc = c;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
static long sum_list(const cell_t *xs) {
|
||||
long acc = 0;
|
||||
while (xs) {
|
||||
acc += xs->head;
|
||||
xs = xs->tail;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
static void free_list(cell_t *xs) {
|
||||
while (xs) {
|
||||
cell_t *next = xs->tail;
|
||||
free(xs);
|
||||
xs = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void run_one(long n) {
|
||||
cell_t *xs = cons_n(n);
|
||||
printf("%ld\n", sum_list(xs));
|
||||
free_list(xs);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
run_one(100000);
|
||||
run_one(1000000);
|
||||
run_one(3000000);
|
||||
return 0;
|
||||
}
|
||||
+1
-1
@@ -61,7 +61,7 @@ mkdir -p "$OUTDIR"
|
||||
|
||||
# Compile both modes for both fixtures up front so the bench loop only
|
||||
# measures runtime, not build time.
|
||||
fixtures=(bench_list_sum bench_tree_walk bench_closure_chain bench_hof_pipeline bench_compute_collatz)
|
||||
fixtures=(bench_list_sum bench_tree_walk bench_closure_chain bench_hof_pipeline bench_compute_collatz bench_list_sum_explicit)
|
||||
modes=(gc bump rc)
|
||||
echo ">>> compiling fixtures (-O2)"
|
||||
for f in "${fixtures[@]}"; do
|
||||
|
||||
+140
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"defs":[{"ctors":[{"fields":[],"name":"INil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"name":"ICons"}],"doc":"Singly-linked Int list. drop-iterative so Own-param drops are O(1) stack.","drop-iterative":true,"kind":"type","name":"IntList"},{"body":{"cond":{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"==","t":"var"},"t":"app"},"else":{"args":[{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"-","t":"var"},"t":"app"},{"args":[{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"-","t":"var"},"t":"app"},{"name":"acc","t":"var"}],"ctor":"ICons","t":"ctor","type":"IntList"}],"fn":{"name":"cons_n_acc","t":"var"},"t":"app","tail":true},"t":"if","then":{"name":"acc","t":"var"}},"doc":"Tail-recursive: prepend (n-1, n-2, ..., 0) onto acc. Returns owned chain.","kind":"fn","name":"cons_n_acc","params":["n","acc"],"type":{"effects":[],"k":"fn","param_modes":["implicit","own"],"params":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"IntList"},"ret_mode":"own"}},{"body":{"args":[{"name":"n","t":"var"},{"args":[],"ctor":"INil","t":"ctor","type":"IntList"}],"fn":{"name":"cons_n_acc","t":"var"},"t":"app"},"doc":"Build [0..n-1] :: IntList. Returns owned chain; caller owns and consumes.","kind":"fn","name":"cons_n","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"IntList"},"ret_mode":"own"}},{"body":{"arms":[{"body":{"name":"acc","t":"var"},"pat":{"ctor":"INil","fields":[],"p":"ctor"}},{"body":{"args":[{"name":"t","t":"var"},{"args":[{"name":"acc","t":"var"},{"name":"h","t":"var"}],"fn":{"name":"+","t":"var"},"t":"app"}],"fn":{"name":"sum_acc","t":"var"},"t":"app","tail":true},"pat":{"ctor":"ICons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Tail-recursive sum. Owns xs; consumes via LCons-arm move-into-tail-call.","kind":"fn","name":"sum_acc","params":["xs","acc"],"type":{"effects":[],"k":"fn","param_modes":["own","implicit"],"params":[{"k":"con","name":"IntList"},{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"name":"xs","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"sum_acc","t":"var"},"t":"app"},"doc":"Sum every element. Owns xs, hands it to sum_acc which consumes it.","kind":"fn","name":"sum_list","params":["xs"],"type":{"effects":[],"k":"fn","param_modes":["own"],"params":[{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"args":[{"args":[{"name":"n","t":"var"}],"fn":{"name":"cons_n","t":"var"},"t":"app"}],"fn":{"name":"sum_list","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"doc":"Build, sum, print. The owned list flows from cons_n into sum_list and is fully consumed.","kind":"fn","name":"run_one","params":["n"],"type":{"effects":["IO"],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Unit"}}},{"body":{"lhs":{"args":[{"lit":{"kind":"int","value":100000},"t":"lit"}],"fn":{"name":"run_one","t":"var"},"t":"app"},"rhs":{"lhs":{"args":[{"lit":{"kind":"int","value":1000000},"t":"lit"}],"fn":{"name":"run_one","t":"var"},"t":"app"},"rhs":{"args":[{"lit":{"kind":"int","value":3000000},"t":"lit"}],"fn":{"name":"run_one","t":"var"},"t":"app"},"t":"seq"},"t":"seq"},"kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"bench_list_sum_explicit","schema":"ailang/v0"}
|
||||
@@ -0,0 +1,94 @@
|
||||
; Bench fixture: explicit-mode pair of bench_list_sum.
|
||||
;
|
||||
; Same algorithm and same sizes as bench_list_sum.ailx (build a list of
|
||||
; 0..N-1, sum it, print) but with full `(borrow)` / `(own)` annotations
|
||||
; on every fn-param signature in the hot path. Under --alloc=rc the
|
||||
; codegen now emits `inc`/`dec` instructions: each cell allocated by
|
||||
; `cons_n_acc` is dec'd as `sum_list_acc` walks the chain.
|
||||
;
|
||||
; The IntList is `(drop-iterative)` so the param drop on the LCons arm
|
||||
; stays O(1) stack regardless of length — without this, dec-ing a
|
||||
; 3M-cell chain at scope close would recursive-overflow.
|
||||
;
|
||||
; This is the "RC-fair" arm of the cross-language bench (21'f);
|
||||
; pairs with bench/reference/list_sum_explicit_free.c which adds
|
||||
; matching free() calls. Together they answer "what does RC's full
|
||||
; alloc+dec cost look like vs C's full malloc+free cost?".
|
||||
;
|
||||
; Sizes match bench_list_sum exactly:
|
||||
; 100_000 * 24 B = 2.4 MB
|
||||
; 1_000_000 * 24 B = 24 MB
|
||||
; 3_000_000 * 24 B = 72 MB
|
||||
;
|
||||
; Expected stdout (one int per line):
|
||||
; 100_000 -> 4999950000
|
||||
; 1_000_000 -> 499999500000
|
||||
; 3_000_000 -> 4499998500000
|
||||
|
||||
(module bench_list_sum_explicit
|
||||
|
||||
(data IntList
|
||||
(doc "Singly-linked Int list. drop-iterative so Own-param drops are O(1) stack.")
|
||||
(ctor INil)
|
||||
(ctor ICons (con Int) (con IntList))
|
||||
(drop-iterative))
|
||||
|
||||
(fn cons_n_acc
|
||||
(doc "Tail-recursive: prepend (n-1, n-2, ..., 0) onto acc. Returns owned chain.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int) (own (con IntList)))
|
||||
(ret (own (con IntList)))))
|
||||
(params n acc)
|
||||
(body
|
||||
(if (app == n 0)
|
||||
acc
|
||||
(tail-app cons_n_acc
|
||||
(app - n 1)
|
||||
(term-ctor IntList ICons (app - n 1) acc)))))
|
||||
|
||||
(fn cons_n
|
||||
(doc "Build [0..n-1] :: IntList. Returns owned chain; caller owns and consumes.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int))
|
||||
(ret (own (con IntList)))))
|
||||
(params n)
|
||||
(body (app cons_n_acc n (term-ctor IntList INil))))
|
||||
|
||||
(fn sum_acc
|
||||
(doc "Tail-recursive sum. Owns xs; consumes via LCons-arm move-into-tail-call.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (own (con IntList)) (con Int))
|
||||
(ret (con Int))))
|
||||
(params xs acc)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor INil) acc)
|
||||
(case (pat-ctor ICons h t)
|
||||
(tail-app sum_acc t (app + acc h))))))
|
||||
|
||||
(fn sum_list
|
||||
(doc "Sum every element. Owns xs, hands it to sum_acc which consumes it.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (own (con IntList)))
|
||||
(ret (con Int))))
|
||||
(params xs)
|
||||
(body (app sum_acc xs 0)))
|
||||
|
||||
(fn run_one
|
||||
(doc "Build, sum, print. The owned list flows from cons_n into sum_list and is fully consumed.")
|
||||
(type (fn-type (params (con Int)) (ret (con Unit)) (effects IO)))
|
||||
(params n)
|
||||
(body
|
||||
(do io/print_int (app sum_list (app cons_n n)))))
|
||||
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(seq (app run_one 100000)
|
||||
(seq (app run_one 1000000)
|
||||
(app run_one 3000000))))))
|
||||
Reference in New Issue
Block a user