Skip to content

Benchmarks — method and results ​

Status: M9a baseline (2026-10-25); memory-copy corrected in M9b (its blocks are permuted, suite README). Master prompt §19 (what to compare and measure) and §22 rule 3 (no performance claim without a reproducible benchmark). The suite is benchmarks/suite/ (its README defines every workload and build); benchmarks/lz4/ and benchmarks/fbvalid/ are the C6 component benchmarks, with their own READMEs.

1. What is compared ​

Each of the sixteen workloads (fifteen from M9a, and nbody from M11 G7d) is written once per language with the same algorithm and data structures (the suite README's table), and prints one checksum, which run.py compares with the expected value before it records a time. A program that computes something else is a failed run, not a fast one.

ConfigurationWhat it isChecks at run time
novanovac build, its C compiled at -O2 (M5 placement off, the default)bounds, overflow, null unwrap, allocation failure
ccc -O2 -std=c11none
cppc++ -O2 -std=c++20, the standard library's containers and unique_ptrnone
rustrustc -C opt-level=2 -C codegen-units=1 (a release build)bounds
rust-checkedthe same with -C overflow-checks=onbounds, overflow: what Nova checks
zigzig build-exe -O ReleaseFast -lc, std.heap.c_allocatornone
zig-safe-O ReleaseSafebounds, overflow, null unwrap: what Nova checks
goosegoose build -O2 (commit 94cc64d)bounds, always, with its own elimination; signed overflow wraps in a release build (Goose docs/reference.md, Overflow and Bounds checks)

nova, c, cpp and goose go through the same C compiler, named by --cc; Rust and Zig use their own LLVM. That is part of what is measured: a language that compiles to C inherits that C compiler's strengths and weaknesses (C6b's fbvalid ran at 1.76× C's time under gcc 12 and equal to C under clang).

Goose has no heap. Three workloads that free objects one at a time have no Goose version, and in three more Goose's nodes share one pool per list or tree (suite README). Those rows compare memory models, not allocators.

2. What is measured ​

MetricHowNotes
Wall timemeasure.c: posix_spawn to wait4, CLOCK_MONOTONIC; one discarded run, then the median of --runs runs (default 5); the minimum and maximum are kept in the JSONprocess start-up is included, under 2 ms here; every workload runs for about 0.4 s or more
Peak resident memoryru_maxrss from wait4 in measure.cmeasured from a small launcher, because Linux carries the forking process's peak across exec (a Python parent showed 13 MiB for every program)
Allocationsallocount.c, preloaded (DYLD_INSERT_LIBRARIES, LD_PRELOAD), in a separate run: calls to malloc, calloc, realloc, posix_memalign, aligned_alloc; bytes requested; peak live heap in usable bytes (malloc_size, malloc_usable_size)includes what the C library and the language runtime allocate before main (about 60 on macOS, 1–10 on Linux); Goose's data stacks are mmap'd and not counted
Compile timewall time of the build command, with each toolchain's global caches warm (one discarded build first); Zig's per-program cache emptied before each buildNova's includes its C compile; Rust's and Zig's include linking their standard libraries
Binary sizethe executable's size on disk, not strippedRust and Zig link their standard library statically

3. Machines ​

NameCPUOSC compilersNote
M1Apple M1, 8 cores (4 performance)macOS 27 (Darwin 27.0.0)Apple clang 21.0.0development machine, shared with other work: used for checksums, not published times
RyzenAMD Ryzen 5 PRO 3600, 6 cores, 12 threadsDebian 12, Linux 6.1clang 18.1.8, gcc 12.2.0shared server: runs under nice, load recorded with each run
Windows VMApple M1 (Parallels VM on the development machine), 4 virtual processorsWindows 11 Pro (ARM64)MSVC 19.51 (ARM64)MSVC only (§4.1); shares the M1 with other work, so its times are compared with its own C only

Toolchains: rustc 1.99.1; Zig 0.17.0 (official tarballs, sha256 checked); Goose 94cc64d built from source.

4. Reproducing ​

sh
dotnet build src/Nova.Cli -c Release
python3 benchmarks/suite/run.py --cc clang --runs 5 --json out/benchmarks/suite/clang.json
python3 benchmarks/suite/run.py --cc gcc --runs 5 --json out/benchmarks/suite/gcc.json

--zig, --goose, --rustc and --novac name the toolchains (a missing one is skipped and reported); --langs and --workloads select rows. --nova-flag=--no-bce (repeatable) passes an option to novac, to measure an optimisation with its switch off (optimisations.md §1); benchmarks/lz4/run.py and benchmarks/fbvalid/run.py take it too.

4.1 MSVC (M9f) ​

run.py needs POSIX (measure.c launches with posix_spawn and reads wait4's peak memory; the allocation counter is an LD_PRELOAD/DYLD interposer), so MSVC has its own runner, benchmarks/suite/run_msvc.py, for Windows:

bat
call "C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat" arm64
dotnet build src\Nova.Cli -c Release
python benchmarks\suite\run_msvc.py --runs 5 --json out\benchmarks\msvc.json
  • Nova: novac build --emit c, then cl /nologo /std:c11 /O2 /link /STACK:8388608 (the stack of c-backend.md §1.1). C: cl /nologo /std:c11 /O2 on the same main.c as elsewhere.
  • The same method otherwise: one discarded run, then the median of --runs wall times (Python's perf_counter, around subprocess.run), and every run's output must equal the workload's checksum.
  • Not measured: peak memory, allocations and compile time. Only Nova and C are compared; the other languages' toolchains are not on the Windows machine.
  • Under MSVC, rt.h uses no compiler builtins (c-backend.md §1.1): checked arithmetic is the portable code. The MSVC row measures that too.

5. Results ​

See docs/milestone-9.md (results), which records each run with its date, machine, load and the commit measured. The acceptance run at the end of M9 is there too (9f): the whole suite in every language, each optimisation's share (benchmarks/suite/attribute.py), and the MSVC row (§4.1).

Pre-1.0: the language and its tools are still changing.