Skip to content

Performance ​

CeeSafe compiles through C, with every safety check on: array bounds, integer overflow, lossy conversions. This page measures what that costs against Rust and plain C on sixteen workloads, and says where CeeSafe is ahead or behind and why.

The result ​

Over the sixteen workloads CeeSafe takes 0.96× Rust's time (geometric mean), and 0.88× that of Rust built with overflow checks, which is the comparison that checks the same things CeeSafe does. Hand-written C without any check takes 0.94×.

WorkloadCeeSafeRustRust, overflow checksCCeeSafe (ms)Rust (ms)
heap-alloc0.281.000.920.95121428
nbody0.821.001.080.82500611
stack-alloc0.841.001.700.85244289
vector-growth0.871.001.030.80385443
arena0.951.000.910.98241254
memory-copy0.991.001.061.13595602
linked-list1.011.000.990.90366361
large-objects1.021.000.971.02254250
small-objects1.031.001.000.89510493
string-building1.031.001.310.78299292
numeric1.121.001.001.14240214
binary-trees1.131.001.020.91561494
parsing1.161.001.101.05161139
data-oriented1.181.001.051.00381323
recursion1.261.001.261.00407323
hash-map1.411.001.170.94453321
geometric mean0.961.001.090.94

Times are each language's median wall time over five runs (after one discarded run) divided by Rust's; lower is faster. Measured 2026-10-31 on an Apple M-series Mac (arm64, macOS), with ceesafec 0.2.0-dev, rustc 1.99.1 and Apple clang 21. A run-to-run spread of a few percent is normal: compare ratios, not single milliseconds.

Peak memory is the same as Rust's or lower everywhere (arena: 7.8 MiB against 17.7 MiB). Binaries are about 40–56 KiB against Rust's 460 KiB. ceesafec compiles each program in about a quarter of a second, roughly twice rustc's time on these single-file programs, since it runs the C compiler after its own work.

Where CeeSafe is ahead ​

  • heap-alloc, 3.5× faster than Rust. ceesafec's placement optimisation keeps a box that never escapes its function on the stack, so the program's per-iteration heap array costs no allocation at all. It beats hand-written C, which calls malloc and free each time.
  • stack-alloc, nbody, vector-growth. CeeSafe matches C. Its overflow and bounds checks are removed where ceesafec proves they cannot fail (loop indexes within a length, sums of bounded values), which Rust with overflow checks does not manage on stack-alloc (1.70×).
  • arena. A region's bump allocation is inlined into the program (Decision 0007), and the arena's chunks take less than half the memory of Rust's.

Where CeeSafe is behind, and why ​

  • hash-map (1.41×). Rust builds its tables with vec![0; n], so LLVM knows their length and removes the bounds checks on keys[s & MASK]. A CeeSafe Vector can only be filled by push, so the length is unknown and every probe keeps its check: with the checks compiled out the program runs at Rust's speed. A filled-vector constructor would close this gap.
  • recursion (1.26×) and parsing (1.16×). The overflow check on fib(n - 1) + fib(n - 2) and on accumulating digits cannot be proven unnecessary; Rust pays the same when its overflow checks are on (1.26× and 1.10×).
  • data-oriented (1.18×). As for hash-map, the particle arrays are filled by push; the bounds checks cost about 11%.
  • binary-trees (1.13×) and small-objects (1.03×). Every heap allocation goes through the runtime's ceesafe_alloc, which counts allocations (for CEESAFE_ALLOC_REPORT and fault injection) before calling malloc. Calling malloc directly measured 12% faster on both.

Const parameters ​

const-flags (ADR 0083 §1.3) is a recursive scanner over 10 MB of pseudo-random bytes, run 100 times, with three flag tests per byte; its flags are const parameters (fn scan<const TS: bool, const JSX: bool>), so each instance keeps only its own tests. It is a workload of run.py, measured on its own and not in the table above, whose sixteen workloads were measured with an older ceesafec. Measured 2026-11-09 on the same kind of machine (Apple arm64, Apple clang 21, rustc 1.99.1, ceesafec 0.26.0-dev), on a machine shared with other work, so the spread is wider than usual:

VersionTime
CeeSafe, const parameters (main.cee)1.05× Rust (2.67 s against 2.55 s, run.py, median of 5)
Rust, const generics (main.rs)1.00
CeeSafe, the same flags as bool parameters (runtime.cee)1.19× CeeSafe's const version (3.68 s against 3.10 s user time, 3 runs each)

At -O2 clang does not clone a recursive function for a constant argument, so a run-time flag keeps every test; the const parameter removes them, as Rust's const generics do. main.c takes the flags as arguments (C has no const parameters), and in the same three runs took 3.27–3.67 s.

Run it yourself ​

sh
python3 benchmarks/run.py                                    # every workload: cee, rust, rust-checked, c; 5 runs
python3 benchmarks/run.py --workloads hash-map,nbody --runs 3
python3 benchmarks/run.py --cee-flag --no-bce                # with bounds-check elimination off

Each workload is the same algorithm with the same data structures in every language, and prints one checksum, which the runner checks. benchmarks/README.md describes the workloads and the build flags. To see which checks ceesafec kept in a program, build it with --explain-bounds-checks, --explain-overflow-checks or --explain-allocations (The ceesafec command).

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