Documentation

kama benchmark results

Generated: 2026-09-21 13:07 · arch: aarch64 (Linux) · in the kama-bench container

Toolchains: clang Ubuntu clang version 18.1.3 (1ubuntu1) · rustc 1.79.0 (129f3b996 2024-06-10) · go version go1.22.5 linux/arm64 · dotnet 8.0.422 · openjdk version "21.0.11" 2026-04-21 · node v22.16.0 · Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio · Python 3.12.3 Timing: hyperfine --warmup 2 --runs 8 --shell=none (median); the (N×) after each time is relative to kama for that workload (native → kama, wasm → kama→wasm). Peak RSS: /usr/bin/time -v.

How to read this (please read before drawing conclusions)#

kama transpiles to C and is compiled by the same clang as the C baseline, so on native compute workloads kama is expected to be within measurement noise of C/C++ — that is the design, not a finding. All four LLVM-AOT languages (C, C++, Rust, kama) are compiled at -O3 for an apples-to-apples comparison — otherwise the optimization level, not the language, dominates a tiny kernel (e.g. fib at C--O2 vs Rust--O3 differs ~20%, but at equal -O3 C and Rust are identical). The signals worth trusting here are:

  1. kama (native) vs managed/interpreted languages (C#, Java, Go, Lua, Python),
  2. peak RSS, compile time, and package size (the low-footprint / self-contained goal),
  3. on the WASM track, kama→wasm vs hand-written JS/TS under the same node.

Methodology — pre-sizing: in map, every language whose standard map has a capacity API pre-sizes it for the keys it will insert (kama Map.withCapacity, C++ reserve, Rust with_capacity, Go make(m, n), C# and Java constructor capacity). Lua, Python and JS have no such API for their built-in table/dict/Map, so they grow from small — the one asymmetry left in that row.

Methodology — run isolated: these are short workloads, so parallel load badly skews them — run the bench with nothing else competing for CPU/IO. (The /work bind mount, virtiofs/9p on macOS/Windows, adds only ~0.3–1.7 ms for native and wasm under a controlled idle measurement — negligible — so artifacts are measured in place.)

WASM is run under node --no-liftoff. V8 compiles wasm in two tiers — Liftoff (baseline: fast to compile, slow to run) then TurboFan (optimizing). For these tiny single-shot processes V8 often never tiers up before exit (worse under load), so default node measured Liftoff wasm — ~4× slower and wildly variable (σ up to 9.8 ms), while JS always got its optimizing JIT. --no-liftoff forces TurboFan, giving optimized, stable wasm (σ ~0.3 ms) — what a real long-running app gets (its hot loops tier up on their own) and a fair compare vs V8's auto-JIT'd JS. Strict IEEE throughout (no -ffast-math).

The compute workloads (fib/pi/collatz) are tuned so the slow interpreters finish quickly; the fast compiled languages run in a few ms, so small absolute differences between them are noise — except dispatch, which measures true dynamic dispatch (see Workloads): the AOT cluster (kama/C/C++/Rust) converges there, while Go's interface dispatch trails ~2×.

Two of these rows measure different things and are named accordingly. map_kernel pins the algorithm, hash, and capacity across every language — a pure codegen number, where the AOT cluster should converge. map lets each language use its idiomatic map — a library-design number, where they legitimately should not. Read the first for "is kama as fast as C?", the second for "how good is the shipped container?". C appears only in map_kernel: it has no stdlib hashmap to enter in map.

The alloc workload is the one to watch for the no-GC story: it churns ~2M growable-list appends and 2000 collection lifetimes, so it contrasts kama's deterministic RAII free against the garbage collectors (Go, C#, Java, Lua, Python, JS) and against the RAII peers (C++ vector, Rust Vec). Watch its peak RSS in particular — GC runtimes keep dead allocations resident until a collection runs.

Fairness gate (checksum equality)#

Every language must produce the same exit-code checksum as kama per workload, or the algorithms have diverged:

Workloads#

NATIVE — execution time (median, ms)#

workload kama C C++ Rust Go C# (JIT) Java (JIT) Lua Python
fib 6.05 (1.0×) 5.95 (1.0×) 6.19 (1.0×) 6.09 (1.0×) 10.09 (1.7×) 33.97 (5.6×) 27.29 (4.5×) 90.06 (14.9×) 213.85 (35.3×)
pi 11.71 (1.0×) 11.84 (1.0×) 11.96 (1.0×) 11.89 (1.0×) 13.92 (1.2×) 32.28 (2.8×) 37.91 (3.2×) 123.02 (10.5×) 1670.39 (142.6×)
collatz 67.07 (1.0×) 67.63 (1.0×) 67.51 (1.0×) 66.86 (1.0×) 91.64 (1.4×) 124.0 (1.8×) 140.03 (2.1×) 981.12 (14.6×) 2957.59 (44.1×)
dispatch 6.48 (1.0×) 6.25 (1.0×) 6.62 (1.0×) 6.52 (1.0×) 13.87 (2.1×) 27.83 (4.3×) 30.5 (4.7×) 103.71 (16.0×) 643.08 (99.2×)
alloc 1.34 (1.0×) 1.14 (0.9×) 1.62 (1.2×) 2.29 (1.7×) 6.66 (5.0×) 27.0 (20.1×) 53.46 (39.9×) 24.96 (18.6×) 109.61 (81.8×)
fnptr 2.48 (1.0×) 2.5 (1.0×) 2.7 (1.1×) 2.67 (1.1×) 5.03 (2.0×) 33.62 (13.6×) 31.67 (12.8×) 138.88 (56.0×) 715.52 (288.5×)
map 5.24 (1.0×) 4.9 (0.9×) 8.27 (1.6×) 23.61 (4.5×) 59.22 (11.3×) 43.09 (8.2×) 7.55 (1.4×) 128.16 (24.5×)
map_kernel 2.73 (1.0×) 2.7 (1.0×) 3.02 (1.1×) 3.03 (1.1×) 4.84 (1.8×) 31.89 (11.7×) 29.09 (10.7×) 52.26 (19.1×) 217.64 (79.7×)
math 3.15 (1.0×) 3.09 (1.0×) 3.28 (1.0×) 3.55 (1.1×) 79.02 (25.1×) 116.97 (37.1×) 113.49 (36.0×) 3916.21 (1243.2×) 4311.81 (1368.8×)

NATIVE — peak resident memory (MB)#

workload kama C C++ Rust Go C# (JIT) Java (JIT) Lua Python
fib 2 2 3 2 2 20 39 2 8
pi 2 2 3 2 2 20 40 2 8
collatz 2 2 3 2 2 20 40 2 8
dispatch 2 2 3 2 2 21 40 2 8
alloc 2 2 3 2 6 25 82 2 8
fnptr 2 2 2 2 2 21 41 2 8
map 7 n/a 7 5 5 23 69 3 21
map_kernel 4 4 5 4 4 24 44 13 18
math 2 2 3 2 2 20 129 2 8

NATIVE — package size#

What you ship: a self-contained binary needs no runtime; managed/interpreted rows are the assembly/source only and additionally require the noted runtime (.NET / JVM / interpreter). The size column is each language's fib artifact; the range spans every workload it built. C# and Java ship one multi-workload assembly, so their size is the same everywhere.

lang package size (fib) range across workloads kind
kama 66.0 KB 66.0 KB – 66.2 KB self-contained
C 66.1 KB 66.1 KB self-contained
C++ 66.1 KB 66.1 KB – 66.3 KB self-contained
Rust 322.3 KB 322.3 KB self-contained
Go 1604.8 KB 1603.7 KB – 1605.2 KB self-contained
C# (JIT) 9.0 KB 9.0 KB + .NET runtime
Java (JIT) 4.9 KB 4.9 KB + JVM
Lua 0.1 KB 0.1 KB – 1.9 KB source (+ Lua)
Python 0.1 KB 0.1 KB – 1.8 KB source (+ Python)

NATIVE — compile time#

Wall-clock to compile that language's bench artifacts (single build, not averaged). The compiled languages build one binary per workload (binaries built — 9 workloads; C skips map); C# and Java build one multi-workload binary that dispatches on args[0]. kama's figure is transpile-to-C plus clang. Interpreted languages (Lua, Python, JS) have no compile step and are omitted.

lang compile time binaries built
kama 1345 ms 9
C 322 ms 8
C++ 758 ms 9
Rust 2058 ms 9
Go 1403 ms 9
C# (JIT) 1481 ms 1
Java (JIT) 301 ms 1

WASM track — execution time under node (median, ms)#

workload kama→wasm JS TS
fib 20.3 (1.0×) 27.57 (1.4×) 27.91 (1.4×)
pi 21.06 (1.0×) 26.47 (1.3×) 26.45 (1.3×)
collatz 95.66 (1.0×) 429.51 (4.5×) 427.28 (4.5×)
dispatch 29.12 (1.0×) 22.99 (0.8×) 22.59 (0.8×)
alloc 18.08 (1.0×) 16.27 (0.9×) 16.58 (0.9×)
fnptr 11.68 (1.0×) 42.73 (3.7×) 41.76 (3.6×)
map 20.15 (1.0×) 48.44 (2.4×)
map_kernel 17.26 (1.0×) 28.76 (1.7×)
math 17.07 (1.0×) 170.15 (10.0×)

WASM track — peak resident memory (MB)#

workload kama→wasm JS TS
fib 43 44 44
pi 43 45 45
collatz 42 45 45
dispatch 45 45 45
alloc 46 46 46
fnptr 42 45 45
map 50 54 n/a
map_kernel 46 50 n/a
math 43 49 n/a

WASM track — module size#

The size column is each language's fib artifact; the range spans every workload it built.

lang package size (fib) range across workloads kind
kama→wasm 0.4 KB 0.2 KB – 9.5 KB + wasm/JS host
JS 0.1 KB 0.1 KB – 1.9 KB source (+ node)
TS 0.1 KB 0.1 KB – 0.6 KB source (+ node)

WASM track — compile time#

kama→wasm is transpile-to-C plus emcc -O3; TS is tsc. Hand-written JS has no compile step.

lang compile time binaries built
kama→wasm 5757 ms 9
TS 244 ms 1

Caveats#

Edit this page on GitHub