Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ee7433c
EIR: one value layout, and ownership modes that survive into the IR
ecto Aug 18, 2026
a8f3e73
Placement is an effect: buffers, kernels, and Place
ecto Aug 18, 2026
588eaad
A device to place things on, and a handler that decides what moves
ecto Aug 18, 2026
1185445
Kernels compile to WGSL, and CI checks the shaders
ecto Aug 18, 2026
a4520ae
Kernels run on a real GPU
ecto Aug 18, 2026
3f55aa4
--place gpu: the same program, on Metal
ecto Aug 18, 2026
3e34eb2
Record a GPU run, replay it without a GPU
ecto Aug 18, 2026
a591d21
Real GPU residency, and the numbers it produces
ecto Aug 18, 2026
9904b9a
Write up v0.9: Placement Is an Effect
ecto Aug 18, 2026
2108dda
A typed kernel executor, and every core
ecto Aug 18, 2026
834b2f6
Launches already pipeline, and the browser claim was wrong
ecto Aug 18, 2026
28f4f2c
Write down the boundary of what placement does
ecto Aug 18, 2026
4ab8e68
Two claims the code was not keeping
ecto Aug 18, 2026
79b54d4
Reductions, without a special case
ecto Aug 18, 2026
6f13fc6
Placed programs run in a browser
ecto Aug 18, 2026
ce0fe80
Parallel placement splits every output, not just one
ecto Aug 18, 2026
00684de
Ship the browser demo through the real build
ecto Aug 18, 2026
dac6022
Restate the boundary now that several items moved
ecto Aug 18, 2026
7a90035
Put the device behind a trait
ecto Aug 19, 2026
3b794fd
WebGPU behind the browser
ecto Aug 19, 2026
61add8d
Two documented mutators exist on no backend that runs by default
ecto Aug 19, 2026
53e861a
The VM could already suspend; I had written down otherwise
ecto Aug 19, 2026
061a68e
A Session that outlives the program it is running
ecto Aug 19, 2026
9d0b9f5
Park and resume from JavaScript, with no isolation required
ecto Aug 19, 2026
1e79544
Merge main: os/ split out, so placement moves to samples/place/
ecto Aug 19, 2026
1d23acd
Golden buffer floats that do not pretend to be constants
ecto Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions BENCHMARKS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
# Benchmarks

## Placement (v0.9.0)

Where a kernel runs is decided by a handler, not by the program. These numbers
say what that costs and what a residency policy is worth.

Harness: `cargo run -q --release -p loon-lang --features gpu --example bench_place`
Machine: Apple M4 Max (Metal, via wgpu). Numbers move a little run to run;
the transfer counts do not move at all.

### What a residency policy is worth

A chain of launches over one 4096-element buffer, on the GPU. The program is
identical in both columns — the only difference is whether `place/resident`
(nine lines, in `samples/place/lib.oo`) is wrapped around it.

| launches | no policy | place/resident | speedup |
|---------:|----------:|---------------:|--------:|
| 8 | 29.3 ms | 8.4 ms | 3.5x |
| 32 | 94.8 ms | 11.2 ms | 8.4x |
| 128 | 358.8 ms | 18.1 ms | 19.9x |

The gap grows with the chain because without a policy every launch uploads its
arguments, computes, and copies its results back; with one, the buffers stay
put and only the final `Place.read` moves anything. A recent Rust GPU-offload
paper measures the same gap at up to 400x between its convenient and explicit
interfaces, and closes it with `Preload`/`PreloadMut` annotations at every call
site plus a transfer-hoisting pass inside LLVM. Here it is a `handle` form.

### Transfers

Exact counts — these do not vary between runs.

| launches | no policy | place/resident | bytes saved |
|---------:|----------:|---------------:|------------:|
| 1 | 2 uploads | 2 uploads | 0 B |
| 4 | 8 uploads | 2 uploads | 96 KB |
| 16 | 32 uploads | 2 uploads | 480 KB |
| 64 | 128 uploads | 2 uploads | 2.0 MB |

### Kernel time: where it runs

The same kernel, the same program, four placements. The CPU columns go through
the typed executor in `eir::kernel_exec` — raw slices, no boxing — so this is a
fair floor rather than a straw man.

| elements | cpu | par | gpu |
|---------:|----:|----:|----:|
| 1,024 | 447 µs | 566 µs | 10.3 ms |
| 16,384 | 853 µs | 647 µs | 7.2 ms |
| 262,144 | 8.9 ms | 3.3 ms | 12.4 ms |
| 1,048,576 | 36.4 ms | 11.4 ms | 19.2 ms |

The interesting row is the last one: on this machine, **every core beats the
GPU** for this kernel at a million elements. An M4 Max has a lot of fast cores,
and a GPU launch pays submission and transfer before it computes anything. That
is not a disappointing result, it is the point — you find it out by changing one
word on the command line, because the program does not know where it runs.

The GPU only wins when the work per element is large enough to amortize getting
there, and where that crossover sits is a property of the machine, not of the
program. Which is a good argument for the decision living outside the program.

This is not a comparison against optimized C or a hand-written kernel. That
comparison is not attempted here and nothing above should be quoted as one.

### Launch overhead

| | ns per launch |
|---|---:|
| cpu | ~6,700 |
| gpu, buffers resident | ~92,000 |

Placement being an effect means every launch is an effect dispatch. That
dispatch is not what you are paying for: an effect operation costs roughly 3x a
function call (see the Effects section), which is nanoseconds, while a GPU
submission is tens of microseconds.

### What is not measured

- Any comparison against hand-written CUDA, HIP, or Metal. Not attempted.
- Reductions and atomics — outside the kernel subset for now.
- f64 on the GPU: WGSL core has no 64-bit scalar, so 64-bit buffers are
computed in 32 bits and that narrowing is reported rather than hidden.

## Collection Benchmarks (v0.5.0)

100,000-element collections using `loop`/`recur` with persistent data structures (imbl).
Expand Down
Loading
Loading