lumbda/bench.py
russell@unturf.com d0247b2729 Add syntax-rules, SRFI-1, stdlib, benchmarks, README
- syntax-rules: full ellipsis support with hygienic bindings (_EllBind)
- define-syntax / let-syntax / letrec-syntax wired up
- cond => arrow form is now TCO (Proc path loops, builtin returns)
- SRFI-1: take-right, drop-right, last, first-fifth, concatenate,
  list-tabulate, unfold, reduce-right, lset-union/intersection/difference,
  proper-list?, dotted-list?, alist-cons, alist-copy, delete, delete-duplicates
- Fix _Nil/_Void/_EOF singletons (is None check vs __bool__)
- Fix flat-map: pass Pair results to _append, not Python lists
- Fix number->string duplicate definition
- stdlib.lsp: 60+ utility functions (string/list/numeric/hash/tree/OOP/coroutines)
- bench.py: 11 benchmarks vs CPython (all correct, ratios 15–1800x)
- README.md: full feature list, examples, usage
- Makefile: add bench target
2026-04-13 11:05:37 -04:00

265 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""
bench.py — benchmarks for uncommonlisp.
Compares interpreter time against equivalent CPython.
Usage: python3 bench.py [-v]
"""
import sys, time, math
from uncommonlisp import make_global_env, read_all, leval, PRELUDE, show
VERBOSE = '-v' in sys.argv
def fresh():
g = make_global_env()
for e in read_all(PRELUDE): leval(e, g)
return g
def run(src, env):
result = None
for e in read_all(src): result = leval(e, env)
return result
def bench(name, lisp_src, python_fn, iters=3):
"""Time both lisp and python implementations; print results."""
env = fresh()
# Warm up
run(lisp_src, env)
python_fn()
# Time Lisp
lisp_times = []
for _ in range(iters):
env2 = fresh()
t = time.perf_counter()
result = run(lisp_src, env2)
lisp_times.append(time.perf_counter() - t)
lisp_best = min(lisp_times)
# Time Python
py_times = []
for _ in range(iters):
t = time.perf_counter()
py_result = python_fn()
py_times.append(time.perf_counter() - t)
py_best = min(py_times)
slowdown = lisp_best / py_best if py_best > 0 else float('inf')
status = '' if result == py_result or str(result) == str(py_result) else ''
print(f'{status} {name:<30} lisp={lisp_best*1000:7.1f}ms py={py_best*1000:7.1f}ms '
f'ratio={slowdown:5.1f}x')
if VERBOSE:
print(f' lisp result: {show(result)!r}')
print(f' py result: {py_result!r}')
return lisp_best, py_best
print('uncommonlisp benchmarks')
print('=' * 75)
# ── 1. Fibonacci (iterative, named let) ──────────────────────────────────────
bench(
'fib(35) named-let',
'''
(define (fib n)
(let loop ((a 0) (b 1) (i 0))
(if (= i n) a (loop b (+ a b) (+ i 1)))))
(fib 35)
''',
lambda: (lambda a=0, b=1, i=0, n=35:
[setattr(sys.modules[__name__], '_fib', None)] and
(lambda: exec('''
def _fib(n):
a, b = 0, 1
for _ in range(n): a, b = b, a+b
return a
''', globals()) or _fib(35))())()
)
# Simpler lambda-based benchmark
def py_fib(n=35):
a, b = 0, 1
for _ in range(n): a, b = b, a + b
return a
bench(
'fib(35) iterative',
'''
(define (fib n)
(let loop ((a 0) (b 1) (i 0))
(if (= i n) a (loop b (+ a b) (+ i 1)))))
(fib 35)
''',
lambda: py_fib(35)
)
# ── 2. fib(20) tree-recursive ─────────────────────────────────────────────────
def py_fib_rec(n):
if n <= 1: return n
return py_fib_rec(n-1) + py_fib_rec(n-2)
bench(
'fib(20) tree-recursive',
'''
(define (fib n)
(if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
(fib 20)
''',
lambda: py_fib_rec(20)
)
# ── 3. Tak function ───────────────────────────────────────────────────────────
def py_tak(x, y, z):
if y >= x: return z
return py_tak(py_tak(x-1,y,z), py_tak(y-1,z,x), py_tak(z-1,x,y))
bench(
'tak(15,10,6)',
'''
(define (tak x y z)
(if (>= y x) z
(tak (tak (- x 1) y z)
(tak (- y 1) z x)
(tak (- z 1) x y))))
(tak 15 10 6)
''',
lambda: py_tak(15, 10, 6)
)
# ── 4. Tail-recursive sum ─────────────────────────────────────────────────────
def py_sum(n):
acc = 0
while n > 0: acc += n; n -= 1
return acc
bench(
'sum-to(50000) tail-recursive',
'''
(define (sum-to n)
(let loop ((i n) (acc 0))
(if (= i 0) acc (loop (- i 1) (+ acc i)))))
(sum-to 50000)
''',
lambda: py_sum(50000)
)
# ── 5. List operations ────────────────────────────────────────────────────────
def py_list_ops():
lst = list(range(5000))
lst = list(reversed(lst))
lst = [x for x in lst if x % 2 == 1]
lst = [x * x for x in lst]
return sum(lst)
bench(
'list ops (5000 elements)',
'''
(define lst (iota 5000))
(define lst (reverse lst))
(define lst (filter odd? lst))
(define lst (map (lambda (x) (* x x)) lst))
(fold-left + 0 lst)
''',
py_list_ops
)
# ── 6. Higher-order / closures ────────────────────────────────────────────────
def py_adder_factory():
adders = [(lambda n: lambda x: x + n)(i) for i in range(100)]
return sum(f(10) for f in adders)
bench(
'closure factory (100 adders)',
'''
(define (make-adder n) (lambda (x) (+ x n)))
(define adders (map make-adder (iota 100)))
(fold-left + 0 (map (lambda (f) (f 10)) adders))
''',
py_adder_factory
)
# ── 7. Hash table ─────────────────────────────────────────────────────────────
def py_hash_ops():
h = {}
for i in range(1000):
h[i] = i * i
return sum(h.get(i, 0) for i in range(1000))
bench(
'hash-table (1000 set+ref)',
'''
(define h (make-hash-table))
(do ((i 0 (+ i 1))) ((= i 1000))
(hash-table-set! h i (* i i)))
(do ((i 0 (+ i 1)) (s 0 (+ s (hash-table-ref/default h i 0))))
((= i 1000) s))
''',
py_hash_ops
)
# ── 8. String operations ──────────────────────────────────────────────────────
def py_string_ops():
parts = [str(i) for i in range(200)]
joined = ', '.join(parts)
return len(joined)
bench(
'string-join (200 numbers)',
'''
(string-length
(string-join (map number->string (iota 200)) ", "))
''',
py_string_ops
)
# ── 9. Ackermann (small) ─────────────────────────────────────────────────────
def py_ack(m, n):
if m == 0: return n + 1
if n == 0: return py_ack(m-1, 1)
return py_ack(m-1, py_ack(m, n-1))
bench(
'ackermann(3,4)',
'''
(define (ack m n)
(cond ((= m 0) (+ n 1))
((= n 0) (ack (- m 1) 1))
(else (ack (- m 1) (ack m (- n 1))))))
(ack 3 4)
''',
lambda: py_ack(3, 4)
)
# ── 10. Mergesort ─────────────────────────────────────────────────────────────
def py_msort(lst):
if len(lst) <= 1: return lst
mid = len(lst) // 2
L = py_msort(lst[:mid]); R = py_msort(lst[mid:])
result = []; i = j = 0
while i < len(L) and j < len(R):
if L[i] <= R[j]: result.append(L[i]); i += 1
else: result.append(R[j]); j += 1
return result + L[i:] + R[j:]
bench(
'mergesort (200 elements)',
'''
(define (merge a b)
(cond ((null? a) b) ((null? b) a)
((< (car a) (car b)) (cons (car a) (merge (cdr a) b)))
(else (cons (car b) (merge a (cdr b))))))
(define (split lst)
(let loop ((l lst) (a (quote ())) (b (quote ())))
(if (null? l) (list a b) (loop (cdr l) b (cons (car l) a)))))
(define (msort lst)
(if (or (null? lst) (null? (cdr lst))) lst
(let ((h (split lst)))
(merge (msort (car h)) (msort (cadr h))))))
(length (msort (reverse (iota 200))))
''',
lambda: len(py_msort(list(range(199, -1, -1))))
)
print('=' * 75)
print('ratio = lisp time / python time (lower is better for lisp)')