How fast is Sisu?
Eight small programs, each written with the same algorithm in Sisu, C, Rust, Go, Java and Python. The numbers show where Sisu stands today, not how fast it will be.
Results
Wall time in seconds, the median of 9 runs. Shorter is faster. Each chart has its own scale. Python takes too long to draw on the same scale as the rest, so its time is given as text.
| Benchmark | Sisu | C | Rust | Go | Java | Python |
|---|---|---|---|---|---|---|
fib | 0.25 | 0.18 | 0.16 | 0.27 | 0.27 | 4.71 |
sieve | 0.18 | 0.19 | 0.17 | 0.20 | 0.24 | 2.27 |
binary_trees | 0.49 | 0.62 | 0.73 | 1.34 | 0.36 | 18.66 |
hello | 0.00 | 0.00 | 0.00 | 0.00 | 0.01 | 0.00 |
hello, build | 0.052 | 0.066 | 0.082 | 0.063 | 0.185 | - |
quicksort | 0.56 | 0.53 | 0.54 | 0.55 | 0.64 | 12.85 |
nbody | 0.13 | 0.14 | 0.11 | 0.23 | 0.20 | 11.23 |
mandelbrot | 0.58 | 0.58 | 0.58 | 0.59 | 0.61 | 17.18 |
fannkuch_redux | 0.12 | 0.11 | 0.12 | 0.12 | 0.16 | 2.76 |
All ran at commit 6ff7970 on 2026-10-25, on an Intel Core i9-14900K under Linux 7.0 (Ubuntu 24.04, glibc 2.39). Each figure is the median of 9 runs, pinned to one CPU. Each table runs from fastest to slowest wall time, with Sisu in bold. The machine was quiet: its load average was below 2, on 32 CPUs, at the start and at the end of the run.
Wall time in seconds, the median of 9 runs. The hello, build row gives build time instead. The full tables, with CPU time, peak memory, binary size and build time, are in docs/benchmarks.md.
These numbers come from one machine, at the commit named above. They change as the compiler changes, and they are rerun by hand when a change should move them.
Where the gaps come from
Each gap was traced with callgrind and hyperfine, and by removing one cost at a time, by hand, from the program's LLVM IR.
binary_trees: the allocator and the counting
Sisu takes 0.79 times as long as C here, because it allocates with mimalloc and C calls glibc's malloc (ADR 0031). Callgrind counted 102 instructions in Sisu's allocator for each allocation and free, against 181 in glibc's, and 11.2 billion instructions for the whole program against C's 16.1 billion. What Sisu pays that C does not is mostly reference counting: a store when an object is made, and a load, subtract and store when it is released. ADR 0005 accepts that cost.
Java is fastest on this program. Its collector allocates by bumping a pointer and frees short-lived objects in bulk. It pays for that with five times the memory Sisu holds.
quicksort: the checks
Each element write checks the index against the bounds and the array's count against 1, and each addition checks for overflow. The checks are the whole gap to C. Removing the overflow checks by hand made no measurable difference.
A loop now makes the arrays it writes unique once, before it starts (ADR 0021). In a separate session on a busy machine, that took the fastest run from 690 ms to 640 ms, against C's 610 ms. The bounds checks remain: LLVM cannot prove the index is in range.
hello: the link step
Lexing, parsing and checking hello take 1.4 ms, timed with hyperfine on one pinned CPU after a warm-up. The whole sisu build -O2 build takes 27.6 ms, and the link through cc alone takes 21.9 ms of that. The bench script neither pins nor warms up its builds, so its 0.052 s reads higher. A faster edit-to-output loop starts with the link, not the compiler.
The programs
Each program lives in its own directory under bench/, with one file per language. All of them print the same output.
- fibComputes fib(40) by naive recursionFunction calls and integer arithmetic.
- sieveCounts the primes up to 50 000 000Array indexing in a tight loop.
- binary_treesThe Benchmarks Game's binary-trees, depth 18Allocating and freeing many small objects.
- helloPrints Hello, World!The time from source to output: build and startup.
- quicksortSorts 10 000 000 pseudo-random integersElement writes, and in Sisu whether an array passed and returned is written in place.
- nbodyThe Benchmarks Game's n-body, 5 000 000 steps
f64arithmetic andsqrt, and field writes through shared objects. - mandelbrotCounts the points of a 4000 by 4000 grid that stay in the set for 50 iterations
f64arithmetic in a tight loop. - fannkuch_reduxThe Benchmarks Game's fannkuch-redux, n = 10Reads and writes of small arrays, with short loops.
How it is measured
One script, scripts/bench.sh, builds each program in every language and times it. make bench runs it.
- Each program uses the same algorithm in every language, written plainly, with no tuning for one language.
- Each figure is the median of 9 runs.
tasksetpins every run to one CPU, so Go and the JVM see a single CPU, and the JVM picks its serial garbage collector. - Wall time is elapsed time. CPU time is user plus system time, so a garbage collector or JIT thread counts too. Both come from
/usr/bin/time. - Every process runs whole, so startup counts: JVM startup and JIT warm-up for Java, interpreter startup for Python.
- The inputs are constants in the source. No compiler folded a program to its answer: the run times grow with the input.
- The checks differ. Sisu checks integer overflow and array bounds. Rust, Go and Java check bounds but not overflow. C checks neither.
- A Sisu binary links its runtime, written in Rust, statically. That accounts for most of its size.
| Language | Toolchain and flags |
|---|---|
| Sisu | sisu build -O2, LLVM 23 |
| C | Ubuntu clang 18.1.3, -O2 |
| Rust | rustc 1.99.0, -C opt-level=3 |
| Go | Go 1.27.2 |
| Java | OpenJDK 25.0.4.1 (Temurin) |
| Python | Python 3.14.5 |
What these numbers cannot tell you
They come from one x86-64 machine, on small single-threaded programs. They show where Sisu stands, not how fast it will be.
Run them yourself
The script builds sisu in release mode, runs each program in every language whose toolchain is on PATH, and skips the rest. If mise installs Go and Java, put them on PATH with mise exec go java -- make bench. It prints the tables with the commit, date and machine.
$ make bench # all benchmarks $ make bench BENCH='fib sieve' # some of them $ RUNS=10 BENCH_CPU=4 make bench
Regression tracking
The tables above compare Sisu with other languages. CodSpeed does something else: it compares Sisu with itself, change by change, so a slowdown shows on the pull request that causes it.
The CodSpeed workflow runs on every push to main. It also runs on a pull request that changes any of these paths:
crates/**andbench/**Cargo.toml,Cargo.lock,rust-toolchain.tomland.cargo/config.toml- the LLVM install:
.github/actions/install-llvm/**andscripts/install-llvm.sh - the benchmark setup:
codspeed.yml,codspeed-main.yml,scripts/codspeed-prep.shand the workflow file
It runs in CodSpeed's simulation mode, which counts instructions rather than timing the run. It measures two things:
- Seven of the programs above, all but
hello, at reduced inputs, and six programs that only CodSpeed runs, inbench/features/:strings,enum_match,shared_arrays,compare,tree_rewriteandinterp. - The compiler itself: an
-O2build ofbench/hello, asisu checkand an-O2build ofbench/binary_trees, asisu checkof a generated 200-section input that uses most of the language, and an-O2build of a 20-section one. Pushes tomainalso build the 200-section input, fromcodspeed-main.yml.
Before it measures, scripts/codspeed-prep.sh builds every benchmark and checks what each one prints. CodSpeed comments on a pull request when a result changes.
The check is informational: it never turns a pull request red. On a push to main, a failed step fails the run, so the benchmarks cannot stop running unnoticed. To compare a change before you push, make callgrind counts the instructions of sisu check on the large input, as CodSpeed does.
Sisu