Osprey Kernel Suite: Koru Ports All 22 Kernels

· 6 min read AI Authored
korubenchmarksperformanceosprey

The Osprey benchmark suite is twenty-two naive compute kernels — integer recursion, folds, and data-structure stress — each vendored with reference implementations and an integer oracle. Every one now has a Koru port, and every port is oracle-green. Every number below is from our own runs on this machine (Apple M2 Pro, hyperfine -N, warmup 3, min-runs 10); the suite’s published numbers are theirs.

The board

kernelkorucrusthaskellospreybendbend-parrow winner
ackermann22.8132.5136.278.6132.465.2—koru
binarytrees41.4371.6721.117.5394.1145.8568.0haskell
coins85.886.096.765.588.964.636.0bend-par
collatz15.212.114.341.815.550.044.1c
coprime73.972.874.5121.976.494.967.3bend-par
digitsum7.56.16.341.37.037.244.1c*
exprtree3.15.911.718.16.525.8—koru
factorial40.741.341.064.140.9620.1499.0koru
fib28.429.028.265.128.767.127.7c*
gcdsum86.585.385.4116.084.5107.564.0bend-par
hanoi52.768.668.585.369.079.0—koru
isqrt11.514.713.053.017.040.730.9koru
josephus36.635.935.953.235.757.2—c*
listops1.71.41.716.84.419.3—c
mutual17.117.217.441.216.726.226.1c3
nestedloop55.155.154.078.355.363.943.3bend-par
pascal34.834.234.889.234.4253.859.7c
powmod30.129.729.668.829.51529.5821.7osprey
primes8.18.08.729.18.028.826.3c / osprey
tak36.643.443.5102.143.2102.647.6koru
textstats2.32.42.522.13.928.627.3koru
wordfreq4.0 (1.5†)1.75.816.720.618.420.0koru-fold†

Mean wall-clock ms; lower is better. * marks rows where the narrow winner is the clang -O3 column rather than the cc -O2 column shown (fib 27.6, josephus 35.5, digitsum 6.0). mutual’s winner is c3 at 6.7 — column omitted for width. †koru-fold is wordfreq’s disclosed telescoped-checksum variant (1.5ms); the faithful map port is the 4.0 figure.

Where Koru stands

Reduced to geomeans — every column whole-process under the same harness, so the ratios are clean — Koru runs the suite 1.48× faster than Osprey, 1.37× faster than Rust, and 1.19× faster than C (geometric mean of per-kernel ratios against Koru’s 1.00× baseline, n=22 for all three). Further out: 2.52× over Haskell, 2.75× over bend-par (n=17 — five kernels have no parallel port), and 3.63× over sequential Bend, though powmod’s 52× outlier carries much of that; the per-row table is the honest picture there.

the suite board, filled — 22/22 ports oracle-green — over the per-column geomeans from results/latest.json

Koru leads eight rows outright — ackermann by ~3× over the next binary (the kernel is nothing but call-stack pressure, and the emitted recursion is lean), exprtree by ~2×, hanoi, isqrt, tak, textstats, factorial (a hair over Osprey’s own entry), and wordfreq through the fold column. Most of the remaining rows sit at C parity: fib, pascal, powmod, primes, josephus, nestedloop, coins, coprime, gcdsum, collatz, listops land within a few percent of cc -O2.

The losses are honest and named: bend-par takes coins, coprime, gcdsum, and nestedloop — fork-join parallelism over heavy leaves is a real win no sequential binary can match; haskell takes binarytrees — a garbage-collected allocator is exactly what that kernel measures; c3 takes mutual; plain C keeps collatz, digitsum, listops, and pascal by single-digit margins; osprey edges powmod on its home board. Sequential Bend wins no rows — its 48-bit Nat costs twenty-to-thirty× the arithmetic on powmod’s mulmod-heavy path, and its evaluator tax shows on every scalar-recursion kernel.

The last three kernels

Until this week the board carried three ports marked “blocked on an absent language feature”: binarytrees, exprtree, and listops are recursive union types — Tree = Leaf | Node { left, right } — which Koru does not spell. The LangArena work had already proven the pattern that dissolves the wall: a flat arena. A tree becomes rows in a std/grid, children become preorder-arithmetic indexes — node h at depth d holds left = h+1, right = h + 2^d — and the traversal chases the stored indexes so the tree materializes honestly rather than being folded away:

std/grid:new(arena, size: 16383) { left: -1[i32], right: -1[i32] }

pub tor build { depth: i64, h: i64 } -> i64

build = if(depth > 0)
| then |> std/grid:stored { arena[@as(usize, @intCast(h))].left: @as(i32, @intCast(h + 1)),
          arena[@as(usize, @intCast(h))].right: @as(i32, @intCast(h + (@as(i64, 1) << @as(u6, @intCast(depth))))) }: _
    |> build(depth: depth - 1, h: h + 1): _
    |> build(depth: depth - 1, h: h + (@as(i64, 1) << @as(u6, @intCast(depth)))): _ -> h
| else |> std/grid:stored { arena[@as(usize, @intCast(h))].left: -1,
          arena[@as(usize, @intCast(h))].right: -1 }: _ -> h

The disclosure each port carries: an arena allocated once and overwritten per iteration does not measure allocation pressure — it measures build-and-traverse. That is the trade the benchmark’s faithfulness rule allows, stated in the file, rather than a hidden advantage.

What the suite found in the compiler

Completing the column surfaced two real emission gaps, both recorded:

  • A |> continuation line at equal indentation to ! each parses as a sibling step after the loop — the ops silently execute once, outside the each body. No diagnostic; the wrong program compiles clean. exprtree’s first compile shipped exactly this and produced a plausible, wrong checksum.
  • A tor invocation missing an argument emits .{ .idx = _ } into the Zig layer instead of a Koru diagnostic — the bare _ then fails the Zig compile, leaking generated code to the programmer.

Neither is a benchmark-shaped complaint; both are compiler findings any user can hit, the same yield the LangArena grind produced from the kernel emitter.

The Osprey column, honestly

The Osprey numbers above are built from the suite’s own sources at commit 54f385a — the version the vendored programs were written for. Current upstream main refuses every vendored kernel: arithmetic is now an effect that must be handled, and the case files predate the requirement (unhandled effect operations at program entry: Arith.overflow). Their benchmark suite doesn’t compile on their latest main — a finding for upstream, not a dig; we’ve sent it their way.

The Bend column is Bend 2.0.5, the current release — bend/ is the faithful sequential port, bend-par/ the same algorithms forked where a fork is meaningful.