Benchmarks

Measured against the languages you'd compare to.

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

Benchmarks for v0.17.0

Strings

append

StringBuilder append throughput — building one large string.

VariantRunCompileOutput
Run timeRAMCompile timeRAMBinary
Rust -O27 ms43.5 MB122 ms118.1 MB3863 KB
Rust -O37 ms43.5 MB124 ms119.5 MB3863 KB
Rust -O19 ms43.5 MB112 ms116.1 MB3863 KB
Axle -O214 ms43.5 MB102 ms123.1 MB305 KB
Axle -O314 ms43.5 MB99 ms122.9 MB305 KB
Axle -O115 ms43.5 MB90 ms122.4 MB305 KB
Axle -O020 ms43.5 MB81 ms124.3 MB306 KB
C++ -O126 ms43.5 MB544 ms118.7 MB17 KB
C++ -O226 ms43.5 MB550 ms118.9 MB17 KB
C++ -O326 ms43.5 MB557 ms119.0 MB17 KB
Rust -O045 ms43.5 MB103 ms116.8 MB3869 KB
C++ -O071 ms43.5 MB523 ms115.1 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 mut 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.
    println(sb.byteLength());
    sb.dispose();
    return 0;
}

churn

StringBuilder grow / reset churn across many small builders.

VariantRunCompileOutput
Run timeRAMCompile timeRAMBinary
Axle -O29 ms43.6 MB114 ms115.8 MB312 KB
Axle -O310 ms43.6 MB113 ms115.7 MB312 KB
Rust -O210 ms43.6 MB130 ms106.7 MB3863 KB
Rust -O310 ms43.6 MB128 ms106.6 MB3863 KB
Axle -O111 ms43.6 MB98 ms115.7 MB312 KB
Rust -O112 ms43.6 MB120 ms105.4 MB3863 KB
C++ -O223 ms43.6 MB557 ms119.0 MB17 KB
C++ -O124 ms43.6 MB552 ms118.7 MB17 KB
C++ -O327 ms43.6 MB576 ms119.0 MB17 KB
Axle -O037 ms43.6 MB90 ms115.7 MB313 KB
Rust -O0155 ms43.6 MB108 ms104.5 MB3870 KB
C++ -O0213 ms43.6 MB527 ms115.3 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 mut acc : i64 = 0;
    let mut o : i64 = 0;
    while (o < outer) {
        let mut 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;
    }

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

int_format

Integer-to-string formatting via StringBuilder.

VariantRunCompileOutput
Run timeRAMCompile timeRAMBinary
Axle -O310 ms43.7 MB108 ms113.1 MB305 KB
C++ -O210 ms43.7 MB567 ms119.1 MB17 KB
C++ -O310 ms43.7 MB560 ms119.1 MB17 KB
Axle -O111 ms43.7 MB94 ms113.1 MB305 KB
Axle -O211 ms43.7 MB110 ms113.2 MB305 KB
C++ -O111 ms43.7 MB559 ms118.7 MB17 KB
Rust -O216 ms43.7 MB133 ms107.3 MB3864 KB
Rust -O317 ms43.7 MB131 ms107.1 MB3863 KB
Axle -O021 ms43.7 MB82 ms113.1 MB307 KB
Rust -O121 ms43.7 MB120 ms106.4 MB3863 KB
Rust -O066 ms43.7 MB106 ms105.1 MB3874 KB
C++ -O0102 ms43.7 MB524 ms115.6 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` writes the digits straight into the builder, where the
// siblings' `std::to_string` / `to_string()` materialise a temporary per
// integer. An API difference, not a language-speed one — read this bench for
// Axle's formatter, not as a like-for-like ratio.
use std::text::StringBuilder;

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

    let mut seed : i64 = 12345;
    let mut 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.
    println(sb.byteLength());
    sb.dispose();
    return 0;
}