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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user