Loft runs a program four ways: the interpreter, native (compiled to Rust via --native), WebAssembly (--native-wasm), and — for comparison — a hand-written Rust version of the same benchmark. The table below shows the time per operation of the ten engine benchmarks in bench/, loft native against its Rust twin.
rustc -C opt-level=3)
Rust reference (rustc -O)
| Benchmark | loft native | Rust | native ÷ Rust |
|---|---|---|---|
01 fibonacci (recursive fib(30) / fib(31)) |
3.14× | ||
| 02 sum loop (5 M steps of a loop-carried integer mix) | 1.81× | ||
| 03 prime sieve (trial division below 300 000) | 1.02× | ||
| 04 Collatz lengths (starts below 200 000) | 1.99× | ||
| 05 Mandelbrot (200×200, 256 iter) | 1.01× | ||
| 06 Newton sqrt (100 000 calls of 50 steps) | 2.21× | ||
| 07 string build (200 000 formatted appends) | 2.12× | ||
| 08 word frequency (hash map, 300 000 operations) | 3.55× | ||
| 09 dot product (2 M floats) | 1.06× | ||
| 10 insertion sort (3 000 integers) | 3.81× |
Time per operation: the median of seven interleaved samples, pinned to the fastest core of an x86-64 Linux machine, and both lanes printed the same result hash. The source is the performance portal (make perf-portal), which also covers the standard library and the published libraries. Run bench/run_bench.sh from the project root for a quick look on your own hardware, with the Python, loft interpreter and loft wasm lanes beside these two.
Key takeaways
The four loft execution modes have very different performance profiles depending on what the program does.
loft interpreter vs Python
The interpreter runs your program without a separate compilation step. Loft is statically typed, so the interpreter never checks the type of a value at run time; its cost is dispatching each bytecode operation and reaching collection data through the store layer. It is slower than CPython on these benchmarks: close to it on an integer loop and on string building, several times slower on hash lookups and float kernels. For hot code, compile with --native.
loft native vs Rust
The native pipeline compiles loft source to Rust via --native or --native-release, then invokes rustc. For floating-point kernels the generated Rust is as fast as hand-written Rust (Mandelbrot 1.01×, dot product 1.06×), and so is trial division (sieve 1.02×). Integer loops run about 2× (sum loop 1.81×, Collatz 1.99×), because loft integer arithmetic is checked: an overflow answers null instead of wrapping. Newton sqrt (2.21×) pays the same for its float division, whose result may be null. Recursion (Fibonacci 3.14×) and collection-heavy code (word count 3.55×, insertion sort 3.81×) are the widest gaps.
loft wasm vs loft native
The wasm build compiles the same generated Rust for wasm32-wasip2, so it runs everywhere wasmtime or a browser is installed. Arithmetic runs close to native speed; text building trails native most, because every text allocation goes through the allocator inside wasm linear memory. Wasm is a good target when native compilation is unavailable.
Bottlenecks
src/codegen_runtime.rs. They perform bounds checks, null-sentinel tests and store-pointer indirections that hand-written Rust avoids.
String allocated inside wasm linear memory, which grows with memory.grow; each append that reallocates costs more than it does natively.
Open performance work
- Native collections without the store layer — emit direct Rust
Vec/HashMapoperations for collections whose ownership allows it, instead ofcodegen_runtimehelper calls. - Native calls without the stores — the compiler knows which functions are pure, and their generated code takes the stores parameter all the same.
- Unchecked integer arithmetic — where the operands are provably non-null and cannot overflow, emit plain Rust arithmetic.
- wasm text representation — a string layout for the wasm target that closes its gap on text building.