lumbda/c/bench.c
russell@unturf.com fc9eb5350c Add C implementation: 7,429 lines, 58 tests, identical output
Complete C port of the Scheme interpreter. Same .lsp files run in
both Python and C with identical output.

Architecture:
- NaN-boxed 64-bit values (zero-alloc numbers)
- Hash-map environments with parent chain + global shortcut
- Interned symbols
- TCO via explicit loop (eval) and TAIL_CALL/SELF_TAIL_CALL (VM)
- Bytecode compiler with all opcodes including superinstructions
- 58 unit + integration tests

Makefile targets:
  make test-all    run Python (571) + C (58) tests
  make examples    run examples in both, compare output
  make friction    benchmark same .lsp in Python vs C
  make c-build     build C interpreter
  make c-test      run C tests
  make c-repl      C REPL
2026-04-14 14:55:17 -04:00

137 lines
4 KiB
C

/*
* bench.c — Benchmarks matching bench.py — same Scheme programs
*/
#include "uncommonlisp.h"
static double bench_time(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1e9;
}
typedef struct {
const char *name;
const char *setup;
const char *expr;
int iterations;
} Benchmark;
static Benchmark benchmarks[] = {
{
"fib-iter(30)",
"(define (fib-iter n)\n"
" (let loop ((a 0) (b 1) (i 0))\n"
" (if (= i n) a (loop b (+ a b) (+ i 1)))))\n",
"(fib-iter 30)",
10000
},
{
"fib-rec(25)",
"(define (fib-rec n)\n"
" (if (<= n 1) n (+ (fib-rec (- n 1)) (fib-rec (- n 2)))))\n",
"(fib-rec 25)",
10
},
{
"list-sum(1000)",
"(define (list-sum lst)\n"
" (let loop ((l lst) (acc 0))\n"
" (if (null? l) acc (loop (cdr l) (+ acc (car l))))))\n"
"(define big-list (iota 1000))\n",
"(list-sum big-list)",
1000
},
{
"map-square(1000)",
"(define (my-map f lst)\n"
" (if (null? lst) '()\n"
" (cons (f (car lst)) (my-map f (cdr lst)))))\n"
"(define big-list (iota 1000))\n",
"(my-map (lambda (x) (* x x)) big-list)",
100
},
{
"tak(18,12,6)",
"(define (tak x y z)\n"
" (if (not (< y x)) z\n"
" (tak (tak (- x 1) y z)\n"
" (tak (- y 1) z x)\n"
" (tak (- z 1) x y))))\n",
"(tak 18 12 6)",
10
},
{NULL, NULL, NULL, 0}
};
static void run_benchmark(Benchmark *b, bool compiled) {
Env *g = make_global_env();
int count;
Value *prelude_exprs = read_all(PRELUDE, &count, false);
for (int i = 0; i < count; i++) leval(prelude_exprs[i], g);
ul_free(prelude_exprs);
if (compiled) g_auto_compile = true;
/* Setup */
Value *setup_exprs = read_all(b->setup, &count, false);
for (int i = 0; i < count; i++) leval(setup_exprs[i], g);
ul_free(setup_exprs);
/* Parse the benchmark expression once */
Value *bench_exprs = read_all(b->expr, &count, false);
/* Warmup */
for (int i = 0; i < 3; i++) {
for (int j = 0; j < count; j++) leval(bench_exprs[j], g);
}
/* Timed run */
double start = bench_time();
for (int i = 0; i < b->iterations; i++) {
for (int j = 0; j < count; j++) leval(bench_exprs[j], g);
}
double elapsed = bench_time() - start;
printf(" %-25s %s %8d iters %.3f s (%.1f us/iter)\n",
b->name, compiled ? "compiled" : "interp ",
b->iterations, elapsed,
(elapsed / b->iterations) * 1e6);
ul_free(bench_exprs);
g_auto_compile = false;
}
int main(void) {
init_symbols();
/* Set up root error context for prelude loading */
ErrorContext root_ctx;
root_ctx.call_stack_depth = 0;
root_ctx.error_obj = VAL_NIL;
root_ctx.source_line = 0;
g_error_ctx = &root_ctx;
if (setjmp(root_ctx.jmp) != 0) {
fprintf(stderr, "fatal error: %s\n", root_ctx.message);
return 1;
}
printf("uncommonlisp C benchmarks\n");
printf("═════════════════════════════════════════════════════════════════\n");
for (int i = 0; benchmarks[i].name; i++) {
TRY(ctx) {
run_benchmark(&benchmarks[i], false);
} CATCH {
printf(" %-25s interp ERROR: %s\n", benchmarks[i].name, ctx.message);
} ENDTRY;
TRY(ctx) {
run_benchmark(&benchmarks[i], true);
} CATCH {
printf(" %-25s compiled ERROR: %s\n", benchmarks[i].name, ctx.message);
} ENDTRY;
}
printf("═════════════════════════════════════════════════════════════════\n");
return 0;
}