lumbda/bench.py
russell@unturf.com f72190d2dc Initial implementation of uncommonlisp
Single-file Scheme-like Lisp interpreter in Python with:
- TCO via explicit while loop (no Python stack overflow at any depth)
- syntax-rules with ellipsis for hygienic macros
- define-macro for procedural macros
- Full numeric tower, strings, chars, vectors, hash tables
- SRFI-1 list library
- call/cc (escape continuations), values, dynamic-wind, guard
- Python interop (py-eval, py-import, py-call, py-attr)
- 396 passing unit/integration/functional tests
- stdlib.lsp with 60+ utility functions
- Benchmark suite vs CPython baseline
2026-04-13 11:01:04 -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(25) 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(25) tree-recursive',
'''
(define (fib n)
(if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
(fib 25)
''',
lambda: py_fib_rec(25)
)
# ── 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(18,12,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 18 12 6)
''',
lambda: py_tak(18, 12, 6)
)
# ── 4. Tail-recursive sum ─────────────────────────────────────────────────────
def py_sum(n):
acc = 0
while n > 0: acc += n; n -= 1
return acc
bench(
'sum-to(100000) tail-recursive',
'''
(define (sum-to n)
(let loop ((i n) (acc 0))
(if (= i 0) acc (loop (- i 1) (+ acc i)))))
(sum-to 100000)
''',
lambda: py_sum(100000)
)
# ── 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,6)',
'''
(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 6)
''',
lambda: py_ack(3, 6)
)
# ── 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 (500 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 500))))
''',
lambda: len(py_msort(list(range(499, -1, -1))))
)
print('=' * 75)
print('ratio = lisp time / python time (lower is better for lisp)')