Benchmarks

Measured against the languages you'd compare to.

Every number comes from axle bench: run time, CPU time, peak RAM at compile and run, and binary size — with the exact source for each language.

Benchmarks for v0.8.6

Strings

append

StringBuilder append throughput — building one large string.

VariantRunCompileOutput
Run timeCPU timeRAMCompile timeRAMBinary
Axle -O05 ms5 ms29.7 MB84 ms117.4 MB330 KB
Axle -O15 ms5 ms29.7 MB81 ms119.9 MB330 KB
Axle -O25 ms5 ms29.7 MB91 ms117.8 MB329 KB
Axle -O35 ms4 ms29.7 MB91 ms119.1 MB329 KB
Rust -O35 ms5 ms29.7 MB124 ms145.8 MB3788 KB
Rust -O26 ms6 ms29.7 MB126 ms144.4 MB3788 KB
Rust -O18 ms8 ms29.7 MB107 ms140.3 MB3788 KB
C++ -O213 ms13 ms34.9 MB499 ms120.8 MB17 KB
Rust -O013 ms13 ms29.7 MB101 ms141.7 MB3791 KB
C++ -O314 ms14 ms34.7 MB519 ms120.9 MB17 KB
C++ -O115 ms15 ms35.0 MB482 ms120.3 MB17 KB
C++ -O051 ms51 ms34.9 MB470 ms117.0 MB30 KB
Source
// EXPECTED_EXIT: 0
// StringBuilder benchmark #1 — raw append throughput.
// Shared logic with append.cpp / append.rs
// (run via `axle bench --compare cpp,rust`).
//
// Workload: append a fixed 16-byte ASCII chunk 1,000,000 times,
// building a 16 MB buffer (~20 capacity-doubling reallocations).
// Output: total length + 3 sampled bytes (start / middle / end) so
// the three languages cross-check byte-identically without an
// O(n^2) full-string walk.
use std::text::StringBuilder;

fn main() : i32 {
    let n : i64 = 1000000;
    let chunk : string = "abcdefghijklmnop"; // 16 ASCII bytes

    let sb : StringBuilder = new StringBuilder();
    let i : i64 = 0;
    while (i < n) {
        sb.append(chunk);
        i = i + 1;
    }

    // O(1) byte length as the cross-impl correctness signal (the
    // workload is deterministic, so identical length ⇒ identical
    // bytes). Reading individual bytes back via `byteAt` would be
    // O(n) per call (AxleString carries no length → `strlen`), which
    // would measure the string ABI, not the builder.
    Console::println(sb.byteLength());
    sb.dispose();
    return 0;
}

churn

StringBuilder grow / reset churn across many small builders.

VariantRunCompileOutput
Run timeCPU timeRAMCompile timeRAMBinary
Rust -O27 ms7 ms29.2 MB117 ms150.2 MB3789 KB
Rust -O38 ms8 ms29.2 MB120 ms150.2 MB3789 KB
Axle -O29 ms9 ms29.2 MB122 ms124.1 MB355 KB
Axle -O010 ms10 ms29.2 MB109 ms125.4 MB355 KB
Axle -O110 ms10 ms29.2 MB110 ms126.2 MB355 KB
Axle -O310 ms10 ms29.2 MB122 ms127.1 MB355 KB
C++ -O220 ms20 ms29.2 MB490 ms120.8 MB17 KB
C++ -O121 ms21 ms29.2 MB488 ms120.5 MB17 KB
C++ -O326 ms26 ms29.2 MB482 ms120.7 MB17 KB
Rust -O127 ms27 ms29.2 MB106 ms146.3 MB3789 KB
Rust -O049 ms49 ms29.2 MB102 ms147.3 MB3795 KB
C++ -O0200 ms199 ms29.2 MB508 ms117.1 MB32 KB
Source
// EXPECTED_EXIT: 0
// StringBuilder benchmark #3 — build / materialize / reset churn.
// Shared logic with churn.cpp / churn.rs (run via `axle bench --compare cpp,rust`).
//
// Workload: 200,000 outer rounds; each appends a 3-byte chunk 20
// times, materialises the result to a `string` (one alloc + copy),
// reads its first byte into an accumulator (so the build can't be
// optimised away), then clear()s for reuse. Stresses the
// append + toString + clear hot loop with buffer reuse. Output:
// the accumulated first-byte sum.
use std::text::StringBuilder;

fn main() : i32 {
    let outer : i64 = 200000;
    let inner : i64 = 20;
    let chunk : string = "xyz";

    let sb : StringBuilder = new StringBuilder();
    let acc : i64 = 0;
    let o : i64 = 0;
    while (o < outer) {
        let k : i64 = 0;
        while (k < inner) {
            sb.append(chunk);
            k = k + 1;
        }
        let s : string = sb.toString();
        acc = acc + (s.byteAt(0) as i64);
        sb.clear();
        o = o + 1;
    }

    Console::println(acc);
    sb.dispose();
    return 0;
}

int_format

Integer-to-string formatting via StringBuilder.

VariantRunCompileOutput
Run timeCPU timeRAMCompile timeRAMBinary
Axle -O18 ms8 ms29.5 MB91 ms117.7 MB330 KB
Axle -O09 ms9 ms29.5 MB93 ms117.8 MB330 KB
Axle -O29 ms9 ms29.5 MB102 ms119.9 MB330 KB
Axle -O39 ms9 ms29.5 MB111 ms120.4 MB330 KB
C++ -O29 ms9 ms29.5 MB491 ms121.0 MB17 KB
C++ -O39 ms9 ms29.5 MB499 ms120.9 MB17 KB
C++ -O110 ms10 ms29.5 MB489 ms120.7 MB17 KB
Rust -O313 ms13 ms29.5 MB128 ms145.0 MB3789 KB
Rust -O216 ms16 ms29.5 MB126 ms147.0 MB3789 KB
Rust -O118 ms18 ms29.5 MB109 ms144.8 MB3789 KB
Rust -O043 ms43 ms29.5 MB99 ms147.9 MB3798 KB
C++ -O0103 ms103 ms29.5 MB479 ms117.4 MB30 KB
Source
// EXPECTED_EXIT: 0
// StringBuilder benchmark #2 — integer formatting throughput.
// Shared logic with int_format.cpp / int_format.rs
// (run via `axle bench --compare cpp,rust`).
//
// Workload: append 500,000 LCG-driven integers (each formatted to
// decimal) separated by commas — a CSV-style builder. Stresses
// `appendI64` (int→string conversion + bulk append) plus the
// comma separator append. Output: total length + first / last byte.
//
// Caveat: `appendI64` formats digits directly into the builder's
// buffer, so it pays no separate string allocation for the formatted
// number. `int_format.cpp`'s `std::to_string` and `int_format.rs`'s
// `to_string()` each materialise a temporary `std::string` / `String`
// per integer before appending it — real work this bench does not ask
// Axle to do, not a language-speed result.
use std::text::StringBuilder;

fn main() : i32 {
    let n : i64 = 500000;
    let sb : StringBuilder = new StringBuilder();

    let seed : i64 = 12345;
    let i : i64 = 0;
    while (i < n) {
        seed = (seed * 48271) % 2147483647;
        sb.appendI64(seed % 100000);
        sb.append(",");
        i = i + 1;
    }

    // O(1) byte length as the cross-impl correctness signal (same
    // LCG + same integer formatting ⇒ identical bytes ⇒ identical
    // length). See append.axle for why per-byte readback is avoided.
    Console::println(sb.byteLength());
    sb.dispose();
    return 0;
}