Skip to content
Open
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
12 changes: 12 additions & 0 deletions .github/goose.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/bash

set -euo pipefail

VERSION=v2026.2
NAME=goose-$VERSION-linux-x64
mkdir -p /tmp/goose
cd /tmp/goose
curl -sSLf -o $NAME.tar.gz https://github.com/aardappel/goose/releases/download/$VERSION/$NAME.tar.gz
tar -xzf $NAME.tar.gz
sudo ln -sf $(pwd)/$NAME/goose /usr/bin/goose
goose version
4 changes: 4 additions & 0 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
# elixir,
# fortran,
go,
goose,
# hacklang,
# haskell,
haxe,
Expand Down Expand Up @@ -157,6 +158,9 @@ jobs:
- name: Install odin
if: matrix.lang == 'odin'
run: ./.github/odin.sh
- name: Install goose
if: matrix.lang == 'goose'
run: ./.github/goose.sh
- uses: dlang-community/setup-dlang@v1
if: matrix.lang == 'd'
with:
Expand Down
49 changes: 49 additions & 0 deletions bench/algorithm/binarytrees/1.goose
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Every tree is a pool of 8-byte nodes local to the function that builds it,
// linked by 4-byte self-relative references; the tree is freed when that
// function returns, by resetting one pointer.

import std;
import os;

let MIN_DEPTH = 4;

struct Node { l: Node&<u32>?, r: Node&<u32>? }

recursive fn make(pool: Node[>..]&, depth: i64) -> Node& {
if depth == 0 { return pool.push(Node {}); }
let l: Node& = make(pool, depth - 1);
let r: Node& = make(pool, depth - 1);
return pool.push(Node { l: l, r: r });
}

recursive fn check(n: Node&) -> i64 {
let l = n.l;
guard l else { return 1; }
let r = n.r;
guard r else { return 1; }
return 1 + check(l) + check(r);
}

fn tree_check(depth: i64) -> i64 {
var pool: Node[>..] = [];
return check(make(pool, depth));
}

fn main() {
let a = args();
var n = 10;
if a.len > 1 { let v, ok = parse_int(a[1]); if ok { n = v; } }
let max_depth = max(MIN_DEPTH + 2, n);
print("stretch tree of depth ", max_depth + 1, "\t check: ", tree_check(max_depth + 1));
var long: Node[>..] = [];
let long_root: Node& = make(long, max_depth);
var d = MIN_DEPTH;
while d <= max_depth {
let iterations = 1 << (max_depth - d + MIN_DEPTH);
var chk = 0;
for i in iterations { chk += tree_check(d); }
print(iterations, "\t trees of depth ", d, "\t check: ", chk);
d += 2;
}
print("long lived tree of depth ", max_depth, "\t check: ", check(long_root));
}
89 changes: 89 additions & 0 deletions bench/algorithm/binarytrees/2-m.goose
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 1.goose's trees, built on a pool of worker threads, one per hardware
// thread. The stretch tree is one job and each depth's iterations are split
// into 16 jobs of equal work; a job's trees are built one after another in
// the worker's own pool, and only the check sums come back. The main thread
// builds the long-lived tree while the workers run.

import std;
import os;

let MIN_DEPTH = 4;
let SPLIT = 16;

struct Node { l: Node&<u32>?, r: Node&<u32>? }

// count trees of the given depth; slot -1 is the stretch tree, slot i the
// depth MIN_DEPTH + 2i, and depth -1 tells a worker to stop.
struct Job { depth: i64, count: i64, slot: i64 }
struct Done { slot: i64, check: i64 }

recursive fn make(pool: Node[>..]&, depth: i64) -> Node& {
if depth == 0 { return pool.push(Node {}); }
let l: Node& = make(pool, depth - 1);
let r: Node& = make(pool, depth - 1);
return pool.push(Node { l: l, r: r });
}

recursive fn check(n: Node&) -> i64 {
let l = n.l;
guard l else { return 1; }
let r = n.r;
guard r else { return 1; }
return 1 + check(l) + check(r);
}

fn tree_check(depth: i64) -> i64 {
var pool: Node[>..] = [];
return check(make(pool, depth));
}

thread_fn worker() {
loop {
let job = qget<Job>();
guard job.depth >= 0 else { break; }
var chk = 0;
for i in job.count { chk += tree_check(job.depth); }
qput(Done { slot: job.slot, check: chk });
}
}

fn main() {
let a = args();
var n = 10;
if a.len > 1 { let v, ok = parse_int(a[1]); if ok { n = v; } }
let max_depth = max(MIN_DEPTH + 2, n);
let depths = (max_depth - MIN_DEPTH) / 2 + 1;

var jobs = 1;
qput(Job { depth: max_depth + 1, count: 1, slot: -1 });
for i in depths {
let iterations = 1 << (max_depth - 2 * i);
let split = min(SPLIT, iterations);
for j in split {
qput(Job { depth: MIN_DEPTH + 2 * i, count: iterations / split, slot: i });
}
jobs += split;
}
let workers = max(1, hardware_threads());
for i in workers { qput(Job { depth: -1, count: 0, slot: 0 }); }
var ids: i64[>..] = [];
for i in workers { ids.push(thread_spawn(worker)); }

var long: Node[>..] = [];
let long_root: Node& = make(long, max_depth);

var stretch = 0;
var sums: i64[>..] = [];
sums.resize(depths, 0);
for k in jobs {
let d = qget<Done>();
if d.slot < 0 { stretch = d.check; } else { sums[d.slot] += d.check; }
}
for id in ids { thread_wait(id); }

print("stretch tree of depth ", max_depth + 1, "\t check: ", stretch);
for i in depths {
print(1 << (max_depth - 2 * i), "\t trees of depth ", MIN_DEPTH + 2 * i, "\t check: ", sums[i]);
}
print("long lived tree of depth ", max_depth, "\t check: ", check(long_root));
}
48 changes: 48 additions & 0 deletions bench/algorithm/coro-prime-sieve/1.goose
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Goose has no coroutines, and its threads cannot form this chain: there is
// one queue per element type, so n filters cannot each have a channel of
// their own, and n is an input of thousands where a worker is an OS thread
// with data stacks of its own. So the chain is the one the generator
// programs (2.cs, 2.d, 2.py) build, a generator followed by one lazy filter
// per prime found, each pulling the next value from the stage before it.
// A stage's state lives in an array instead of a heap-allocated frame:
// the generator's counter, and a filter's prime. Asking stage k for a value
// is a call and handing it back a return, as resume and suspend are in the
// Zig program; every value visits the filters in the order the channels
// would hand it on, and a new filter joins at the end of the chain. The
// values are u32, as in the Zig program.

import std;
import os;

// The next value of stage k, k == 0 being the generator.
recursive fn pull(primes: u32[:], k: i64, gen: u32&) -> u32 {
if k == 0 {
let v = gen;
gen += 1;
return v;
}
// The prime is read inside the loop: hoisted into a local, the compiler
// precomputes a reciprocal for the `%` with a 128-bit division per call,
// where a call here divides about once.
loop {
let v = pull(primes, k - 1, gen);
if v % primes[k - 1] != 0 { return v; }
}
}

fn main() {
let a = args();
var n = 100;
if a.len > 1 { let v, ok = parse_int(a[1]); if ok { n = v; } }
var primes: u32[>..] = [];
var gen: u32 = 2;
// Goose's stdout is unbuffered, so the lines are collected and written
// at once, as the references' buffered writers do.
var out: u8[>..] = [];
for i in n {
let prime = pull(primes, primes.len, gen);
format(out, prime, "\n");
primes.push(prime);
}
write_stdout(out);
}
Loading