Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Benchmark suite

on:
schedule:
- cron: "0 3 * * *" # nightly at 03:00 UTC
workflow_dispatch: # manual trigger

permissions:
contents: write # to push results.json

jobs:
bench:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-bench-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{ runner.os }}-bench-

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Run benchmark suite
run: bash bench/run.sh --no-rust

- name: Compile Rust baselines and re-run
run: |
mkdir -p bench/.build
for d in bench/*/; do
b=$(basename "$d")
rs="$d$b.rs"
if [ -f "$rs" ]; then
rustc -O -o "bench/.build/${b}_rs" "$rs" || true
fi
done
bash bench/run.sh

- name: Check for regression (>10% vs previous)
run: |
if [ -f bench/results-prev.json ]; then
python3 - bench/results.json bench/results-prev.json << 'PYEOF'
import sys, json
cur = json.load(open(sys.argv[1]))["benchmarks"]
prev = json.load(open(sys.argv[2]))["benchmarks"]
failures = []
for bench, langs in cur.items():
for lang, ns in langs.items():
prev_ns = prev.get(bench, {}).get(lang)
if prev_ns and ns > prev_ns * 1.10:
pct = (ns - prev_ns) / prev_ns * 100
failures.append(f" {bench}/{lang}: {prev_ns}ns -> {ns}ns (+{pct:.1f}%)")
if failures:
print("REGRESSION DETECTED (>10%):")
for f in failures:
print(f)
sys.exit(1)
else:
print("No regressions detected.")
PYEOF
fi

- name: Rotate results
run: |
cp bench/results.json bench/results-prev.json 2>/dev/null || true

- name: Commit updated results
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore(bench): update nightly results [skip ci]"
file_pattern: "bench/results.json bench/results-prev.json"
branch: main
1 change: 1 addition & 0 deletions bench/fib/fib.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fib n:n>n;<=n 1 n;a=fib -n 1;b=fib -n 2;+a b
17 changes: 17 additions & 0 deletions bench/fib/fib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}

const N = parseInt(process.argv[2]) || 25;
// warmup
for (let i = 0; i < 100; i++) fib(N);
const iters = 1000;
const start = process.hrtime.bigint();
let r;
for (let i = 0; i < iters; i++) r = fib(N);
const elapsed = Number(process.hrtime.bigint() - start);
console.log(`result: ${r}`);
console.log(`iterations: ${iters}`);
console.log(`total: ${(elapsed / 1e6).toFixed(2)}ms`);
console.log(`per call: ${Math.floor(elapsed / iters)}ns`);
20 changes: 20 additions & 0 deletions bench/fib/fib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import sys, time

def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)

N = int(sys.argv[1]) if len(sys.argv) > 1 else 25
# warmup
for _ in range(5):
fib(N)
iters = 100
start = time.monotonic_ns()
for _ in range(iters):
r = fib(N)
elapsed = time.monotonic_ns() - start
print(f"result: {r}")
print(f"iterations: {iters}")
print(f"total: {elapsed / 1e6:.2f}ms")
print(f"per call: {elapsed // iters}ns")
22 changes: 22 additions & 0 deletions bench/fib/fib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::env;
use std::time::Instant;

fn fib(n: i64) -> i64 {
if n <= 1 { return n; }
fib(n - 1) + fib(n - 2)
}

fn main() {
let n: i64 = env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(25);
// warmup
for _ in 0..100 { let _ = fib(n); }
let iters = 1000;
let start = Instant::now();
let mut r = 0i64;
for _ in 0..iters { r = fib(n); }
let elapsed = start.elapsed().as_nanos();
println!("result: {}", r);
println!("iterations: {}", iters);
println!("total: {:.2}ms", elapsed as f64 / 1e6);
println!("per call: {}ns", elapsed / iters);
}
1 change: 1 addition & 0 deletions bench/hof/hof.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bench n:n>n;xs=[];i=0;wh <i n{xs=+=xs i;i=+i 1};fld (a:n b:n>n;+a b) (flt (x:n>b;>x 0) (map (x:n>n;*x x) xs)) 0
16 changes: 16 additions & 0 deletions bench/hof/hof.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function bench(n) {
const xs = Array.from({length: n}, (_, i) => i);
return xs.map(x => x * x).filter(x => x > 0).reduce((a, b) => a + b, 0);
}

const N = parseInt(process.argv[2]) || 500;
for (let i = 0; i < 1000; i++) bench(N);
const iters = 10000;
const start = process.hrtime.bigint();
let r;
for (let i = 0; i < iters; i++) r = bench(N);
const elapsed = Number(process.hrtime.bigint() - start);
console.log(`result: ${r}`);
console.log(`iterations: ${iters}`);
console.log(`total: ${(elapsed / 1e6).toFixed(2)}ms`);
console.log(`per call: ${Math.floor(elapsed / iters)}ns`);
23 changes: 23 additions & 0 deletions bench/hof/hof.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys, time
from functools import reduce

def sq(x): return x * x
def pos(x): return x > 0
def add(a, b): return a + b

def bench(n):
xs = list(range(n))
return reduce(add, filter(pos, map(sq, xs)), 0)

N = int(sys.argv[1]) if len(sys.argv) > 1 else 500
for _ in range(100):
bench(N)
iters = 10000
start = time.monotonic_ns()
for _ in range(iters):
r = bench(N)
elapsed = time.monotonic_ns() - start
print(f"result: {r}")
print(f"iterations: {iters}")
print(f"total: {elapsed / 1e6:.2f}ms")
print(f"per call: {elapsed // iters}ns")
20 changes: 20 additions & 0 deletions bench/hof/hof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::env;
use std::time::Instant;

fn bench(n: i64) -> i64 {
(0..n).map(|x| x * x).filter(|&x| x > 0).sum()
}

fn main() {
let n: i64 = env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(500);
for _ in 0..1000 { let _ = bench(n); }
let iters: u128 = 10000;
let start = Instant::now();
let mut r = 0i64;
for _ in 0..iters { r = bench(n); }
let elapsed = start.elapsed().as_nanos();
println!("result: {}", r);
println!("iterations: {}", iters);
println!("total: {:.2}ms", elapsed as f64 / 1e6);
println!("per call: {}ns", elapsed / iters);
}
2 changes: 2 additions & 0 deletions bench/listproc/listproc.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lev x:n>n;>=x 5000 5;>=x 3000 4;>=x 1000 3;>=x 500 2;1
bench n:n>n;s=0;i=0;wh <i n{a=*i 3;b=+a 1;c=*b 2;d=lev c;s=+s d;i=+i 1};+s 0
31 changes: 31 additions & 0 deletions bench/listproc/listproc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
function lev(x) {
if (x >= 5000) return 5;
if (x >= 3000) return 4;
if (x >= 1000) return 3;
if (x >= 500) return 2;
return 1;
}

function bench(n) {
let s = 0;
for (let i = 0; i < n; i++) {
const a = i * 3;
const b = a + 1;
const c = b * 2;
const d = lev(c);
s += d;
}
return s;
}

const N = parseInt(process.argv[2]) || 1000;
for (let i = 0; i < 1000; i++) bench(N);
const iters = 10000;
const start = process.hrtime.bigint();
let r;
for (let i = 0; i < iters; i++) r = bench(N);
const elapsed = Number(process.hrtime.bigint() - start);
console.log(`result: ${r}`);
console.log(`iterations: ${iters}`);
console.log(`total: ${(elapsed / 1e6).toFixed(2)}ms`);
console.log(`per call: ${Math.floor(elapsed / iters)}ns`);
31 changes: 31 additions & 0 deletions bench/listproc/listproc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import sys, time

def lev(x):
if x >= 5000: return 5
if x >= 3000: return 4
if x >= 1000: return 3
if x >= 500: return 2
return 1

def bench(n):
s = 0
for i in range(n):
a = i * 3
b = a + 1
c = b * 2
d = lev(c)
s += d
return s

N = int(sys.argv[1]) if len(sys.argv) > 1 else 1000
for _ in range(100):
bench(N)
iters = 10000
start = time.monotonic_ns()
for _ in range(iters):
r = bench(N)
elapsed = time.monotonic_ns() - start
print(f"result: {r}")
print(f"iterations: {iters}")
print(f"total: {elapsed / 1e6:.2f}ms")
print(f"per call: {elapsed // iters}ns")
37 changes: 37 additions & 0 deletions bench/listproc/listproc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::env;
use std::time::Instant;

#[inline(never)]
fn lev(x: i64) -> i64 {
if x >= 5000 { return 5; }
if x >= 3000 { return 4; }
if x >= 1000 { return 3; }
if x >= 500 { return 2; }
1
}

fn bench(n: i64) -> i64 {
let mut s: i64 = 0;
for i in 0..n {
let a = i * 3;
let b = a + 1;
let c = b * 2;
let d = lev(c);
s += d;
}
s
}

fn main() {
let n: i64 = env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(1000);
for _ in 0..1000 { let _ = bench(n); }
let iters: u128 = 10000;
let start = Instant::now();
let mut r = 0i64;
for _ in 0..iters { r = bench(n); }
let elapsed = start.elapsed().as_nanos();
println!("result: {}", r);
println!("iterations: {}", iters);
println!("total: {:.2}ms", elapsed as f64 / 1e6);
println!("per call: {}ns", elapsed / iters);
}
4 changes: 4 additions & 0 deletions bench/pattern-match/pattern-match.ilo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cata x:n>n;>=x 900 9;>=x 800 8;>=x 700 7;>=x 600 6;>=x 500 5;>=x 400 4;>=x 300 3;>=x 200 2;1
catb x:n>n;>=x 500 *x 3;>=x 200 *x 2;+x 0
combine a:n b:n>n;>=a 7 +b *a 10;>=a 4 +b *a 5;+b a
bench n:n>n;s=0;i=0;wh <i n{a=cata i;b=catb i;c=combine a b;s=+s c;i=+i 1};+s 0
42 changes: 42 additions & 0 deletions bench/pattern-match/pattern-match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
function cata(x) {
if (x >= 800) { if (x >= 900) return 9; return 8; }
if (x >= 600) { if (x >= 700) return 7; return 6; }
if (x >= 400) { if (x >= 500) return 5; return 4; }
if (x >= 200) { if (x >= 300) return 3; return 2; }
return 1;
}

function catb(x) {
if (x >= 500) return x * 3;
if (x >= 200) return x * 2;
return x;
}

function combine(a, b) {
if (a >= 7) return b + a * 10;
if (a >= 4) return b + a * 5;
return b + a;
}

function bench(n) {
let s = 0;
for (let i = 0; i < n; i++) {
const a = cata(i);
const b = catb(i);
const c = combine(a, b);
s += c;
}
return s;
}

const N = parseInt(process.argv[2]) || 1000;
for (let i = 0; i < 1000; i++) bench(N);
const iters = 10000;
const start = process.hrtime.bigint();
let r;
for (let i = 0; i < iters; i++) r = bench(N);
const elapsed = Number(process.hrtime.bigint() - start);
console.log(`result: ${r}`);
console.log(`iterations: ${iters}`);
console.log(`total: ${(elapsed / 1e6).toFixed(2)}ms`);
console.log(`per call: ${Math.floor(elapsed / iters)}ns`);
Loading
Loading