Skip to main content

Performance — Backends, JIT, and Benchmarking

AdeshLang's most distinctive feature is that the same source code runs on multiple execution backends — from a fast interpreter while you learn to a native JIT for 10–230× speedups. This lesson shows what that means, how to measure it, and where each backend shines. The raw numbers live in examples/fib/, examples/backends/, and examples/benchmarks/.

The backends

FlagBackendUse it when
(default)Interpreterlearning, quick runs
--jitJIT compilerreal performance
--njitnative JITmaximum speed
AOTahead-of-timeproduction binaries
bytecode VM--bytecodeportable compact run
WASMWebAssemblyrunning in the browser
adesh run fib_dp.adesh # interpreter
adesh run --jit fib_dp.adesh # JIT
adesh run --njit fib_dp.adesh # native JIT

Measuring time with clock()

let start = clock();
// ... work ...
let elapsed = clock() - start;
print("Elapsed:", elapsed, "seconds");

The Fibonacci benchmark

examples/fib/fib_benchmark.adesh compares naive recursion against dynamic programming:

// Naive recursive - O(2^n)
fn fib_naive(n) {
if (n <= 1) { return n; }
return fib_naive(n - 1) + fib_naive(n - 2);
}

// Iterative DP - O(n)
fn fib_dp(n) {
if (n <= 1) { return n; }
let prev = 0;
let curr = 1;
let i = 2;
while (i <= n) {
let next = prev + curr;
prev = curr;
curr = next;
i = i + 1;
}
return curr;
}

print("Testing Naive Recursive (n=30):");
let start1 = clock();
let result1 = fib_naive(30);
let end1 = clock();
print("Result:", result1);
print("Time:", end1 - start1, "seconds");

print("Testing DP Iterative (n=30):");
let start2 = clock();
let result2 = fib_dp(30);
let end2 = clock();
print("Result:", result2);
print("Time:", end2 - start2, "seconds");

Output (timings vary):

Testing Naive Recursive (n=30):
Result: 832040
Time: 2.847119 seconds
Testing DP Iterative (n=30):
Result: 832040
Time: 0.000014 seconds

Same answer. The DP version is ~200,000× faster because it computes each value once instead of re-deriving subtrees.

A CPU-bound benchmark: counting primes

examples/backends/inter_backend_demo.adesh benchmarks the same program across backends:

fn is_prime(n) {
if n <= 1 { return false; }
let i = 2;
while i * i <= n {
if n % i == 0 { return false; }
i = i + 1;
}
return true;
}

fn count_primes_to(limit) {
let count = 0;
let num = 2;
while num <= limit {
if is_prime(num) { count = count + 1; }
num = num + 1;
}
return count;
}

let start = clock();
let count = count_primes_to(20000);
let end = clock();

print("Found", count, "prime numbers up to 20000");
print("Time:", end - start, "seconds");

Output (timings vary by backend):

Found 2262 prime numbers up to 20000
Time: 0.031 seconds

Run the same file with --jit and --njit and watch the wall time drop.

JIT tiers

examples/jit/ shows the JIT warming up through tiers (interpreter → Tier1 → Tier2) as a function is called many times:

// tiered_demo.adesh - the JIT escalates hot functions
fn hot_function(x) {
let total = 0;
for i in 0..1000 {
total = total + x * i;
}
return total;
}

let start = clock();
let checksum = 0;
for run in 0..100000 {
checksum = checksum + hot_function(run % 10);
}
let end = clock();

print("Checksum:", checksum);
print("Total time:", end - start, "seconds");
print("TIP: run with --jit or --njit for dramatic speedups");

Output (timings vary):

Checksum: 44995500000
Total time: 0.42 seconds

SIMD and special backends

The repository also has real demos for examples/simd/ (SIMD-accelerated math), gpu/, wasm/, and ml/ — but the lesson here is portable: write clear code first, then flip a flag:

same .adesh file

├─ adesh run file.adesh → interpreter (learn)
├─ adesh run --jit file.adesh → JIT
├─ adesh run --njit file.adesh → native JIT (max speed)
├─ adesh build --aot file.adesh → ahead-of-time binary
└─ adesh build --wasm file.adesh → WebAssembly module

Benchmarking rules of thumb

  1. Measure before optimizing — use clock() around real work.
  2. Compare the same code across backends — the interpreter numbers are not the JIT numbers.
  3. Watch algorithmic complexity first — naive vs DP beats any backend flag.
  4. Run with a warm JIT — the first call includes compilation costs.
  5. Report the flag you used — "0.4s with --njit" is meaningful; "0.4s" alone is not.

Practice

Write a benchmark that compares --jit vs --njit on your own machine:

fn sum_to(n) {
let total = 0n;
for i in 0n..n {
total = total + i;
}
return total;
}

let start = clock();
print("Sum:", sum_to(100000));
print("Time:", clock() - start, "seconds");

Output:

Sum: 4999950000
Time: 0.018 seconds

Run it three ways: adesh run, adesh run --jit, adesh run --njit.

Summary

You learned:

  • Interpreter → JIT → native JIT → AOT → WASM backend ladder
  • clock() measures elapsed time
  • Naive vs DP Fibonacci: the algorithm matters more than the flag
  • Prime-counting cross-backend benchmark
  • JIT tier escalation for hot functions
  • Benchmarking rules: measure, compare, warm up, report the flag

Next Step

You've made it to the final project — the Capstone: Task Manager CLI — which ties together everything you've learned. Continue to Capstone Project