Benchmarks
Measured cross-runtime benchmark results — one Geekbench-style suite run inside a real Compose desktop app across JVM JIT, GraalVM Native Image, SwiftUI, Tauri, and Flutter.
The benchmark-demo
example runs one Geekbench-style suite — 13 CPU kernels, geometric render ramps, and a list-load
bench — inside a real Compose desktop app, then re-implements the same kernels byte-for-byte in
SwiftUI, Tauri, and Flutter. Every variant runs the identical suite on the same machine, so the
numbers are directly comparable. This page presents the measured results.
Setup
- Date: 2026-07-16
- Machine: Apple M4, 10 cores, AC power, thermally stable
- Toolchains: Oracle GraalVM 25.1.3+9.1 (Java 25.0.3), OpenJDK 26.0.1, Gradle daemon JBR 21
- Protocol: each variant self-runs its suite (3 discarded warmups, then best-of-5 over the CPU kernels). Runs are strictly sequential, one app at a time; peak RSS is sampled during each run. Built-in self-checks pass everywhere.
Composite scores are only comparable within an identical suite. Every value below comes from the same suite and the same machine session. CPU and GFX: higher is better. list, peak RAM, and app size: lower is better.
Summary
Sorted by composite CPU score.
| # | Variant | Runtime | CPU | GFX | list (ms) | Peak RAM | App size |
|---|---|---|---|---|---|---|---|
| 1 | jvm-graal | GraalVM 25.0.3 JIT + ProGuard | 146.7 | 13041 | 21.0 | 695 MB | 160 MB |
| 2 | tauri | Rust + WebView (WKWebView) | 142.2 | 15013 | 233.0 | 794 MB † | 5.6 MB ‡ |
| 3 | jvm-c2-pg | OpenJDK 26 C2 + ProGuard | 140.4 | 13041 | 20.9 | 616 MB | 119 MB |
| 4 | swiftui | Swift/LLVM + SwiftUI | 138.7 | 15254 | 14.4 | 268 MB | 0.34 MB ‡ |
| 5 | jvm-c2 | OpenJDK 26 C2 (no ProGuard) | 138.5 | 13041 | 21.6 | 620 MB | 136 MB |
| 6 | aot-o3-pgo | GraalVM Native -O3 + PGO | 132.7 | 13859 | 17.1 | 269 MB | 129 MB |
| 7 | aot-o3 | GraalVM Native -O3 (ML-inferred) | 100.8 | 13859 | 17.1 | 297 MB | 174 MB |
| 8 | aot-o2 | GraalVM Native -O2 | 86.9 | 11239 | 17.2 | 278 MB | 150 MB |
| 9 | aot-os | GraalVM Native size-opt (-Os) | 80.1 | 10433 | 17.3 | 326 MB | 129 MB |
| 10 | flutter | Dart AOT + Impeller | 57.3 | 15133 | 17.2 | 196 MB | 37 MB |
Key takeaways
- Raw CPU. GraalVM JIT leads (146.7). The LLVM baselines (Tauri 142, SwiftUI 139) and OpenJDK C2 (138–140) are bunched together. GraalVM Native only catches the JVM/LLVM pack with PGO (100.8 → 132.7); without a profile,
matmulcollapses (3877 vs 12225 MFLOP/s).-O2and-Ostrail further behind. - The PGO sweet spot.
aot-o3-pgoreaches near-JVM CPU (132.7) with 2.5× lower RAM (269 MB vs 616–695 MB), a compact.app(129 MB), and fast startup. The cost is the PGO flow: instrumented build → GUI run → rebuild. PGO also shrinks the image (174 MB → 129 MB). - RAM. Flutter is the leanest (196 MB), ahead of SwiftUI (268) and GraalVM Native (269–326). The JVMs sit at 616–695 MB. Tauri is the heaviest (794 MB) once the WebKit process tree is counted (†).
- Startup / list. Native ~17 ms, JVM ~21 ms, SwiftUI 14 ms (best), Tauri 233 ms (WebView DOM/canvas list rendering — an order of magnitude slower).
- Lowest CPU is Flutter (57.3):
raytracerat a quarter of the pack, weakmatmul/sha/sieve— but it compensates with top-tier rendering (GFX 15133).
Tauri RAM — the multi-process WebView pitfall (†)
Tauri/WKWebView is multi-process on macOS. The canvas and JS run in separate WebKit processes
(com.apple.WebKit.WebContent, .GPU, .Networking), not in the app binary. The main-process
RSS alone is 177 MB, but the real footprint — summing the WebKit process tree — is 794 MB, making
Tauri the heaviest variant in RAM, not the lightest. (Summing RSS slightly overcounts shared
WebKit.framework pages, so 794 MB is an upper bound; the 177→794 gap is large and real.)
App size — self-contained vs system runtime (‡)
The self-contained variants ship their own runtime, so their weight is the full .app (runtime +
Skiko/dylibs + jars/binary): JVM (jlink), GraalVM Native, Flutter. SwiftUI and Tauri rely on the OS
runtime — SwiftUI on the system SwiftUI/AppKit, Tauri on the system WebKit — so their binaries
(0.34 / 5.6 MB) exclude the engine macOS provides. The two size classes are not directly comparable.
Per-kernel CPU
Throughput, higher is better.
| Kernel | jvm-graal | tauri | jvm-c2-pg | swiftui | jvm-c2 | aot-o3-pgo | aot-o3 | aot-o2 | aot-os | flutter |
|---|---|---|---|---|---|---|---|---|---|---|
| mandelbrot | 4.09 | 4.13 | 4.33 | 3.92 | 3.99 | 1.98 | 3.32 | 1.97 | 3.27 | 3.61 |
| nbody | 700.6 | 693.2 | 639.6 | 659.4 | 691.3 | 694.7 | 683.0 | 654.8 | 678.3 | 608.7 |
| raytracer | 17.2 | 19.8 | 18.9 | 19.8 | 19.0 | 17.5 | 15.8 | 14.0 | 7.0 | 4.2 |
| matmul | 14217 | 16224 | 16690 | 16215 | 16724 | 12225 | 3877 | 3244 | 3105 | 3054 |
| blur | 93.4 | 44.5 | 59.7 | 80.7 | 49.2 | 91.5 | 49.5 | 46.6 | 36.5 | 30.0 |
| sha256 | 332.9 | 442.1 | 361.2 | 360.3 | 361.7 | 326.9 | 296.8 | 294.9 | 268.2 | 192.4 |
| sieve | 708.5 | 642.3 | 565.9 | 603.6 | 599.9 | 725.9 | 467.2 | 463.2 | 484.4 | 268.6 |
| fft | 407.8 | 452.7 | 441.5 | 436.9 | 441.1 | 363.9 | 369.6 | 376.3 | 356.8 | 366.9 |
| pi | 0.09 | 0.11 | 0.11 | 0.11 | 0.10 | 0.10 | 0.09 | 0.09 | 0.09 | 0.08 |
| mandelbrot_mt | 15.5 | 16.5 | 15.6 | 16.1 | 16.1 | 14.5 | 14.1 | 7.5 | 12.2 | 10.6 |
| raytracer_mt | 74.5 | 81.2 | 68.6 | 75.0 | 67.2 | 71.4 | 60.2 | 49.9 | 25.5 | 13.7 |
| matmul_mt | 62088 | 69442 | 64038 | 33013 | 63925 | 60113 | 19988 | 17255 | 15956 | 9731 |
| blur_mt | 345.0 | 188.4 | 244.1 | 261.7 | 238.3 | 285.9 | 203.5 | 194.4 | 155.1 | 40.0 |
The Graal compiler signature (JIT and AOT+PGO alike) is strong blur/sieve, dull fft/sha. The
known PGO regression on mandelbrot reproduces (1.98 with the profile vs 3.32 without).
Render ramps
Maximum sustained load at ≥ 55 fps, higher is better.
| Variant | Particles | Stars | Texts |
|---|---|---|---|
| swiftui | 125000 | 18175 | 3906 |
| aot-o3 / o3-pgo | 75000 | 14540 | 6102 |
| jvm (×3) | 50000 | 18175 | 6102 |
| flutter | 50000 | 18175 | 9533 |
| aot-o2 | 50000 | 11632 | 6102 |
| aot-os | 50000 | 9306 | 6102 |
| tauri | 25000 | 18175 | 18618 |
Reproduce
cd examples/benchmark-demo
# JVM + native matrix (homogeneous RAM):
JAVA_HOME=<jbr-21> GRAALVM_HOME=<graalvm-25> JDK_C2_HOME=<jdk-26> \
ONLY="jvm-c2 jvm-c2-pg jvm-graal aot-os aot-o2 aot-o3 aot-o3-pgo" ./run-all.sh
# ports:
ONLY="swiftui tauri flutter" ./run-all.shNative builds must run with JAVA_HOME=<graalvm> (the current JVM must be GraalVM). Otherwise a
stale Gradle-provisioned Oracle JDK 25 without native-image can match the vendor=ORACLE,
version=25 spec and get selected, failing the compile. Each variant writes a result JSON; the
full per-variant appendix lives in the example's RESULTS.md.
What's next
- GraalVM overview — what native-image gives a Nucleus app.
- Configuration — set up the PGO profile behind the
aot-o3-pgoresult. - AOT cache — JDK 25+ startup on the JVM path.
Performance and native runtimes
Nucleus builds the same Kotlin source into two runtimes — a GraalVM native image or a JDK 25 image with an AOT cache — selected from one Gradle DSL.
GraalVM Native Image
GraalVM Native Image compiles a Nucleus app ahead of time into a self-contained native binary, with reflection, resource, and JNI metadata generated for every Nucleus module.