|
| 1 | +//! Benchmarks for tree-walker and Cranelift JIT. |
| 2 | +//! |
| 3 | +//! We focus on: |
| 4 | +//! |
| 5 | +//! - **fib** — classic deep non-tail recursion (call-overhead dominated). |
| 6 | +//! - **tail_recursion** — accumulator-style loop. Before TCO landed this |
| 7 | +//! required the 64MB stack hack; now both backends loop in O(1) stack. |
| 8 | +//! - **stdlib_reduce** — exercises the inline-compiled `List.reduce` |
| 9 | +//! helper in the JIT. |
| 10 | +//! - **fizzbuzz** — `List.map` over a range with string interpolation |
| 11 | +//! (tree-walker only — `jit::run` is numeric-result-only). |
| 12 | +//! - **parse** — front-end only, to track parser changes in isolation. |
| 13 | +//! |
| 14 | +//! For JIT timings we compile once outside `b.iter` and only measure the |
| 15 | +//! actual `Compiled::run`. Compiling inside `iter` would both inflate the |
| 16 | +//! number with codegen cost and leak executable pages every iteration — |
| 17 | +//! `jit::run` is fire-and-forget on purpose. |
1 | 18 | use criterion::{criterion_group, criterion_main, Criterion}; |
2 | | -use spctr::{interp, parser, resolver}; |
| 19 | +use spctr::{interp, jit, parser, resolver}; |
3 | 20 | use std::hint::black_box; |
4 | 21 |
|
5 | | -fn run_program(src: &str) { |
| 22 | +fn parse_and_resolve(src: &str) -> spctr::ast::Statement { |
6 | 23 | let ast = parser::parse(src).expect("parse failed"); |
7 | 24 | resolver::resolve(&ast, &interp::ROOT_NAMES).expect("resolve failed"); |
8 | | - let v = interp::run(&ast).expect("interpret failed"); |
9 | | - black_box(v); |
| 25 | + ast |
10 | 26 | } |
11 | 27 |
|
12 | 28 | fn bench_fib(c: &mut Criterion) { |
13 | 29 | let mut group = c.benchmark_group("fib"); |
14 | 30 | for n in [10u32, 20, 25] { |
15 | 31 | let src = format!( |
16 | | - "fib: (n) => if n < 2 then n else fib(n-1) + fib(n-2), fib({})", |
| 32 | + "fib: (n) => if n < 2 then n else fib(n - 1) + fib(n - 2), fib({})", |
17 | 33 | n |
18 | 34 | ); |
19 | | - group.bench_function(format!("fib({})", n), |b| { |
20 | | - b.iter(|| run_program(&src)); |
| 35 | + let ast = parse_and_resolve(&src); |
| 36 | + let compiled = jit::compile(&ast).expect("jit compile"); |
| 37 | + group.bench_function(format!("interp/fib({})", n), |b| { |
| 38 | + b.iter(|| black_box(interp::run(&ast).expect("interp"))); |
| 39 | + }); |
| 40 | + group.bench_function(format!("jit/fib({})", n), |b| { |
| 41 | + b.iter(|| black_box(compiled.run())); |
| 42 | + }); |
| 43 | + } |
| 44 | + group.finish(); |
| 45 | +} |
| 46 | + |
| 47 | +fn bench_tail_recursion(c: &mut Criterion) { |
| 48 | + let mut group = c.benchmark_group("tail_recursion"); |
| 49 | + for n in [1_000u32, 10_000, 100_000] { |
| 50 | + let src = format!( |
| 51 | + "loop_n: (n, acc) => if n == 0 then acc else loop_n(n - 1, acc + 1), loop_n({}, 0)", |
| 52 | + n |
| 53 | + ); |
| 54 | + let ast = parse_and_resolve(&src); |
| 55 | + let compiled = jit::compile(&ast).expect("jit compile"); |
| 56 | + group.bench_function(format!("interp/loop({})", n), |b| { |
| 57 | + b.iter(|| black_box(interp::run(&ast).expect("interp"))); |
| 58 | + }); |
| 59 | + group.bench_function(format!("jit/loop({})", n), |b| { |
| 60 | + b.iter(|| black_box(compiled.run())); |
| 61 | + }); |
| 62 | + } |
| 63 | + group.finish(); |
| 64 | +} |
| 65 | + |
| 66 | +fn bench_stdlib_reduce(c: &mut Criterion) { |
| 67 | + let mut group = c.benchmark_group("stdlib_reduce"); |
| 68 | + for n in [100u32, 1_000, 10_000] { |
| 69 | + let src = format!( |
| 70 | + "List.reduce(List.range(0, {}), 0, (acc, x) => acc + x)", |
| 71 | + n |
| 72 | + ); |
| 73 | + let ast = parse_and_resolve(&src); |
| 74 | + let compiled = jit::compile(&ast).expect("jit compile"); |
| 75 | + group.bench_function(format!("interp/sum_range({})", n), |b| { |
| 76 | + b.iter(|| black_box(interp::run(&ast).expect("interp"))); |
| 77 | + }); |
| 78 | + group.bench_function(format!("jit/sum_range({})", n), |b| { |
| 79 | + b.iter(|| black_box(compiled.run())); |
21 | 80 | }); |
22 | 81 | } |
23 | 82 | group.finish(); |
24 | 83 | } |
25 | 84 |
|
26 | 85 | fn bench_fizzbuzz(c: &mut Criterion) { |
27 | 86 | let mut group = c.benchmark_group("fizzbuzz"); |
28 | | - for n in [100u32, 1000] { |
| 87 | + for n in [100u32, 1_000] { |
29 | 88 | let src = format!( |
30 | 89 | r#" |
31 | | -range: Iterator.range(0, {}), |
32 | | -fizzbuzz: (i) => {{ |
33 | | - is_fizz: i % 3 == 0, |
34 | | - is_buzz: i % 5 == 0, |
35 | | - fizz: if is_fizz then "fizz" else "", |
36 | | - buzz: if is_buzz then "buzz" else "", |
37 | | - String.concat(fizz, buzz) |
38 | | -}}, |
39 | | -range.map((i) => [i, fizzbuzz(i)]).to_list |
| 90 | +fizzbuzz: (i) => |
| 91 | + if i % 15 == 0 then "FizzBuzz" |
| 92 | + else if i % 3 == 0 then "Fizz" |
| 93 | + else if i % 5 == 0 then "Buzz" |
| 94 | + else "${{i}}", |
| 95 | +List.map(List.range(0, {}), fizzbuzz) |
40 | 96 | "#, |
41 | 97 | n |
42 | 98 | ); |
43 | | - group.bench_function(format!("fizzbuzz({})", n), |b| { |
44 | | - b.iter(|| run_program(&src)); |
| 99 | + let ast = parse_and_resolve(&src); |
| 100 | + group.bench_function(format!("interp/fizzbuzz({})", n), |b| { |
| 101 | + b.iter(|| black_box(interp::run(&ast).expect("interp"))); |
45 | 102 | }); |
46 | 103 | } |
47 | 104 | group.finish(); |
48 | 105 | } |
49 | 106 |
|
50 | 107 | fn bench_parse_only(c: &mut Criterion) { |
51 | 108 | let src = include_str!("../examples/fizzbuzz.spc"); |
52 | | - c.bench_function("parse fizzbuzz.spc", |b| { |
53 | | - b.iter(|| { |
54 | | - let ast = parser::parse(black_box(src)).expect("parse"); |
55 | | - black_box(ast); |
56 | | - }); |
| 109 | + c.bench_function("parse/fizzbuzz.spc", |b| { |
| 110 | + b.iter(|| black_box(parser::parse(black_box(src)).expect("parse"))); |
57 | 111 | }); |
58 | 112 | } |
59 | 113 |
|
60 | | -criterion_group!(benches, bench_fib, bench_fizzbuzz, bench_parse_only); |
| 114 | +criterion_group!( |
| 115 | + benches, |
| 116 | + bench_fib, |
| 117 | + bench_tail_recursion, |
| 118 | + bench_stdlib_reduce, |
| 119 | + bench_fizzbuzz, |
| 120 | + bench_parse_only, |
| 121 | +); |
61 | 122 | criterion_main!(benches); |
0 commit comments