Files
AILang/examples/bench_closure_chain.ailx
T
Brummel 07bff24527 bench: 21'b — closure-pair + HOF/poly fixtures, 47-metric baseline
Two new throughput fixtures targeting blind spots in the 21'a
corpus:

- bench_closure_chain exercises the build_pair_drop_fn codegen
  path (the 18c.4 doubled-braces trigger). Each iteration of
  run_loop allocates a {thunk, env} closure pair via the
  let-rec-name-as-value escape route. Sizes 10k / 100k / 500k.
  rc/bump = 4.14x — materially worse than the 2.91x / 2.59x of
  the linear/tree fixtures, exposing that closure work pays the
  RC alloc tax twice (pair + env-struct).

- bench_hof_pipeline exercises poly-ADT instantiation and
  indirect dispatch via fold_with_fn over List<a>. Sizes 100k /
  1M / 3M elements. Ratios essentially match bench_list_sum,
  confirming the 13b static-template-plus-ctor-inline design
  has zero measurable overhead at this scale.

Baseline file extends from 31 to 47 metrics. The two new fixtures
build clean under all three allocators; the rc-arm build exercises
the per-type drop fn for the closure-pair, providing a tripwire
for any future 18c.4-class IR malformedness.

JOURNAL records both surprises (4.14x closure tax, ~zero HOF/poly
overhead) and explicitly notes the dispersion observation on
explicit_at_rc.p99 — three captures today (357.5 / 294.6 / 251.5)
confirm wide run-to-run variance on that fixture. Methodology
upgrade (n>=10 captures or tighter fixture) deferred to 21'c.

bench/run.sh fixtures array updated. bench/check.py needed no
changes — its parser handles the wider table by metric name.
2026-05-09 00:55:57 +02:00

69 lines
2.4 KiB
Plaintext

; Bench fixture: closure-pair allocation under load.
;
; Each iteration of `run_loop` introduces a fresh `let-rec helper`
; whose body captures the outer fn-param `i`. The let-rec name is
; passed as a value to `apply_thrice`, which forces eta-Lam wrapping
; (the 16b.5 / 8b ABI shape) — codegen allocates a `{ thunk, env }`
; closure pair where `env` holds the captured `i`. Under
; --alloc=rc the allocation goes through `ailang_rc_alloc` and the
; per-type drop fn `build_pair_drop_fn` is generated for the
; closure-pair type. (The 18c.4 doubled-braces bug lived in exactly
; this drop fn; this fixture is the canonical exerciser.)
;
; The helper body is (body i) — it ignores its parameter and
; returns the capture. apply_thrice(helper, x) therefore evaluates
; to i regardless of x. Each run_loop iteration thus contributes
; `i` to the accumulator. Total = sum 0..n-1 = n*(n-1)/2.
;
; Sizes (Implicit-mode RC leaks the closure pairs by design — the
; gc/bump arms are the meaningful allocator comparison, the rc arm
; reports alloc-tax-only):
; n = 10_000 -> 49_995_000 (1k closure-pair allocs)
; n = 100_000 -> 4_999_950_000 (100k allocs)
; n = 500_000 -> 124_999_750_000 (500k allocs)
(module bench_closure_chain
(fn apply_thrice
(doc "Higher-order: apply f three times to seed.")
(type
(fn-type
(params (fn-type (params (con Int)) (ret (con Int)))
(con Int))
(ret (con Int))))
(params f seed)
(body
(app f (app f (app f seed)))))
(fn run_loop
(doc "For each i in [n-1, n-2, ..., 0], build a closure capturing i, pass to apply_thrice, accumulate. Tail-recursive on i and acc.")
(type
(fn-type
(params (con Int) (con Int))
(ret (con Int))))
(params i acc)
(body
(if (app < i 0)
acc
(let-rec helper
(params x)
(type (fn-type (params (con Int)) (ret (con Int))))
(body i)
(in
(let r (app apply_thrice helper i)
(tail-app run_loop (app - i 1) (app + acc r))))))))
(fn run
(doc "Drive run_loop from i=n-1 down to 0.")
(type (fn-type (params (con Int)) (ret (con Int))))
(params n)
(body (app run_loop (app - n 1) 0)))
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(seq (do io/print_int (app run 10000))
(seq (do io/print_int (app run 100000))
(do io/print_int (app run 500000)))))))