Benchmark datasheet
Plutus VM Benchmark
How fast Cardano Plutus (UPLC) virtual machines run the same scripts, and whether their results are correct.
- Revision (run date)
- Correctness gate
- Scripts
- scripts
- Harness commit
256b6aafc- Test machine
AMD EPYC-Milan Processor· 16 cores · 61Gi · 3 parallel lanes · 2026-10-27 · harness256b6aafcTest conditions
RankingRanked on decode + eval. The same scripts for every VM.
Ranking
Decode + eval is the ranking metric: each timed loop decodes the flat script, then evaluates it, as a node does with a script it has not seen. Eval only times a script decoded before the loop. A VM that compiles each script before timing has only an eval-only time, so it is not ranked. The scripts’ Plutus version and cost model are in Test conditions.
Select a row for its details. Select a script name in the per-script table to rank that script here.
This benchmark compares the speed of Plutus virtual machines (UPLC evaluators), the programs that run Cardano smart contracts. Each VM decodes and evaluates the same 89 Plutus scripts. A correctness gate checks every result and its execution units (ExUnits) against plutus-core, the Haskell CEK machine of the Cardano node. This run of 2026-10-27 has 15 evaluators, written in Scala, Go, Rust, Haskell, Scala.js, TypeScript, C#, Java, Zig and C++. The method and the source are open.
Summary of all VMs – all VMs, geometric mean over 89 scripts
| # | VM | Language / runtime | Decode + eval | Eval only | vs fastest | Check |
|---|---|---|---|---|---|---|
| 1 | Scalus CEK | Scala / JVM | 268.19 µs | 170.83 µs | 1.00× | pass |
| 2 | Plutigo | Go | 306.48 µs | 204.65 µs | 1.14× | pass |
| 3 | amaru-uplc | Rust | 467.01 µs | 365.22 µs | 1.74× | pass |
| 4 | plutus-core | Haskell / GHC | 710.63 µs | 276.06 µs | 2.65× | pass |
| 5 | Scalus.js CEK | Scala.js / Node V8 | 1.46 ms | 980.88 µs | 5.46× | pass |
| 6 | Aiken uplc | Rust | 8.22 ms | 6.21 ms | 30.66× | pass |
| – | Aiken uplc WASM | Rust → WASM / Node V8 | – | 884.88 µs | eval only | pass |
| – | Scalus JIT (experimental: no downstream user) | Scala / JVM | – | 94.41 µs | not ranked | pass |
| – | blaze-plutus (experimental: no downstream user) | TypeScript / Node V8 | 1.29 ms | 1.06 ms | not ranked | pass |
| – | Chrysalis (experimental: pre-release only) | C# / .NET JIT | 536.58 µs | 401.63 µs | not ranked | pass |
| – | Chrysalis AOT (experimental: pre-release only) | C# / .NET AOT | 586.35 µs | 447.74 µs | not ranked | pass |
| – | Julc Java CEK (experimental: pre-release only) | Java / JVM | 487.78 µs | 392.40 µs | not ranked | pass |
| – | Plutuz (experimental: no release) | Zig | 517.88 µs | 335.45 µs | not ranked | pass |
| – | llvm-uplc JIT (experimental: outdated) | C++ / LLVM | – | 111.08 µs | not ranked | outdated cost model |
| – | amaru-uplc BC (fork) (experimental: no release) | Rust | 340.86 µs | 220.89 µs | not ranked | pass |
Geometric mean of the time per script over the 89 scripts. Decode + eval is the ranking metric. A VM that fails or skips a script gets the slowest competitor's time for it. Eval only times a program that was decoded before the timed loop. A dash means that the run has no such time for the VM. A VM that compiles a script before it runs it decodes and compiles each script once, before timing. So it has only an eval-only time, and no decode + eval time. The VMs use different execution models, so compare VMs of the same model too (Execution Model). Experimental VMs are shown, but not ranked. Aiken WASM caches decoded programs inside the library, so its time is shown as eval only.
Per-script resultsSort by any column. A script name loads it into the ranking.
Mean time per script
Each VM has two columns: eval only (decode excluded), then decode + eval (the ranking metric). A VM that compiles each script before timing has only the eval-only column. Green bold marks the fastest ranked VM on a script, per column type. An experimental VM is never marked. Select a header to sort. Select a script name to rank that script in the ranking. The buttons hide and show VMs.
HistoryEach run is a revision. One colour per VM, as in the ranking.
Revision history
The benchmark runs again when a VM or the harness changes. Each run is one revision of this datasheet. The history chart plots the same runs.
| Revision | Run date | VMs timed | Fastest vs Haskell |
|---|
Test conditionsThe conditions of this revision.
All VMs run on the same server, in the same run, with the same harness. These are the conditions of this revision.
| Scripts | Plutus scripts, the same for every VM. |
|---|---|
| Reference | Haskell plutus-core, the CEK machine of the Cardano node.Every VM must return the same result and the same ExUnits on every script. |
| Isolation | Each VM runs in its own Docker image, with its native benchmark framework. |
| VM versions | Pinned for this run. See VMs and toolchains. |
| Harness | Commit 256b6aafc |
| Reference drift | A small drift means the server stayed stable during the run. |
| Noise band | ±5 %Servers of the same type differ by up to this much between runs. A smaller change counts as no change. |
Full method – how each loop is timed and how the geometric means are taken.
VMs and toolchainsRanked fastest first, then experimental, then failed check. Versions pinned for this run.
| VM | Pinned version | Links | Used by | Status and notes |
|---|---|---|---|---|
| Scalus CEK (Scala / JVM)Scala, GraalVM, Interpreter (CEK) | v1.4.1 | source scalus3/scalushome scalus.org | Hydrozoa L2 proofBifrost bridge proofCardano Client Lib SDK proofYaci Store indexer proofYaci DevKit dev tool proof | |
| Plutigo (Go)Go, native, Interpreter (CEK) | v0.1.10 | source blinklabs-io/plutigo | Dingo node proof | |
| amaru-uplc (Rust)Rust, native, Interpreter (AST walker) | amaru-uplc-v0.4.0 | source pragma-org/uplc | Amaru node proof | |
| plutus-core (Haskell / GHC)Haskell, GHC, Interpreter (CEK) | 1.70.0.0 | source IntersectMBO/plutushome plutus.cardano.intersectmbo.org | cardano-node node proof | |
| Scalus.js CEK (Scala.js / Node V8)Scala.js, Node (V8), Interpreter (CEK) | scalus 1.4.1 (npm) | source scalus3/scalushome scalus.org | Lucid Evolution SDK proofMeshJS SDK proofEvolution SDK SDK proof | |
| Aiken uplc (Rust)Rust, native, Interpreter (CEK) | v1.1.24 | source aiken-lang/aikenhome aiken-lang.org | Aiken compiler proofBlaze SDK SDK proof | |
| Aiken uplc WASM (Rust → WASM / Node V8)Rust → WASM, Node (V8), Interpreter (CEK) | @lucid-evolution/uplc 0.4.0 | source Anastasia-Labs/lucid-evolutionhome anastasia-labs.github.io/lucid-evolution | Lucid Evolution SDK proof | decode cached (by the library) Built from colll78/aiken (aiken main plus fixes), which includes the CEK fixes that v1.1.24 lacks. |
| Scalus JIT (Scala / JVM)Scala, GraalVM, JIT compiler (UPLC → JVM bytecode) | v1.4.1 | source scalus3/scalushome scalus.org | none yet | experimental: no downstream userdecode and compile excluded |
| blaze-plutus (TypeScript / Node V8)TypeScript, Node (V8), Interpreter (CEK) | @blaze-cardano/plutus 0.1.1 | source butaneprotocol/blaze-cardanohome blaze.butane.dev | none yet | experimental: no downstream user |
| Chrysalis (C# / .NET JIT)C#, .NET 10 (JIT), Interpreter (CEK) | Chrysalis.Plutus 1.7.14-alpha (NuGet) | source SAIB-Inc/Chrysalis | none yet | experimental: pre-release only |
| Chrysalis AOT (C# / .NET AOT)C#, .NET 10 (NativeAOT), Interpreter (CEK) | Chrysalis.Plutus 1.7.14-alpha (NuGet) | source SAIB-Inc/Chrysalis | none yet | experimental: pre-release only |
| Julc Java CEK (Java / JVM)Java, GraalVM, Interpreter (CEK) | v0.1.0-pre19 | source bloxbean/julc | none yet | experimental: pre-release only |
| Plutuz (Zig)Zig, native, Interpreter (CEK) | main 33b812d | source utxo-company/plutuz | none yet | experimental: no release |
| llvm-uplc JIT (C++ / LLVM)C / C++, native (LLVM 22 LLJIT), JIT compiler (UPLC → native code) | main 6bcef67 | source SeungheonOh/llvm-uplc | none yet | outdated cost modelexperimental: outdateddecode and compile excluded |
| amaru-uplc BC (fork) (Rust)Rust, native, Bytecode VM (UPLC → bytecode) | fork 069cef5 | source jonathanlim222/uplc | none yet | experimental: no release |
Execution modelWhy the ranking has categories.
VMs run UPLC in different ways. A UPLC-level JIT compiler does different work from a CEK interpreter, even when both run on a JIT-compiled host such as the JVM or V8. Numbers in one category compare like for like. Numbers across categories do not compare directly.
Method
Each script has two timings. Eval only evaluates a program that was decoded once, before timing. Decode + eval decodes the flat script and evaluates it in every iteration, as a node does with a script it has not seen. The ranking uses decode + eval. Decoding is a large share of the time for some VMs (about 66 % for Haskell plutus-core), so eval only shows how fast the evaluator alone is.
Correctness gate: each VM evaluates every script once more and reports the result and the ExUnits. They must equal Haskell plutus-core. A VM that differs is not ranked (“failed check”). A VM whose API cannot take the mainnet cost model is ranked with an “outdated cost model” badge, if it equals Haskell with the model the VM ships.
A VM that compiles a script before it runs it (“decode and compile excluded”) decodes and compiles each script once, before timing. Its timed loop runs the compiled script, so neither step is in its time. It has only an eval-only time and is not ranked. Its compile time is shown separately. A VM whose library caches decoded programs (“decode cached (by the library)”) decodes a script only on the first call, in the warm-up. Its timed loop runs the cache lookup and the evaluation, so its two columns have the same numbers.
Flat decode and evaluation on Plutus scripts. Each VM uses its native benchmark framework and runs in its own Docker image. Geometric means in a category use only the scripts where every VM in that category succeeded, so all VMs in a category are compared on the same scripts.
FAQPlutus VMs and this benchmark.
What is a Plutus VM?
A Plutus VM, or UPLC evaluator, runs Untyped Plutus Core (UPLC), the language of Cardano smart contracts. The Cardano node uses the CEK machine of plutus-core (Haskell) to validate scripts. Wallets, SDKs, emulators and other tools use their own evaluators to compute the script result and its execution units (ExUnits) before they submit a transaction.
What does this benchmark measure?
Each VM flat-decodes and evaluates 89 Plutus scripts from the plutus-use-cases test suite (auction, escrow, uniswap, stablecoin and others). The page shows the mean time per script and the geometric mean over all scripts. Each VM runs in its own Docker image on the same server type, one VM at a time. A VM is ranked only if its results and ExUnits equal Haskell plutus-core on every script.
Which Plutus VM is the fastest?
In the run of 2026-10-27, Scalus CEK (Scala / JVM) has the lowest decode + eval geometric mean of the ranked VMs: 268.19 µs. plutus-core, the Haskell CEK machine of the Cardano node, takes 710.63 µs (2.65× the fastest time). The VMs use different execution models (CEK interpreter, AST walker, JIT compiler), so compare VMs of the same model too. Experimental VMs are not ranked.
Why does the ranking include the decode time?
A node decodes a script from its flat encoding before it evaluates it. For a script that the node has not seen, both steps take time. For some VMs the decode is a large share of the time (about 66 % for Haskell plutus-core), so the page also shows an eval-only time.
How often is the benchmark updated?
A GitHub Actions workflow runs the full benchmark on the 1st of each month, and the maintainers start extra runs when a VM release changes. The workflow skips a scheduled run when nothing changed. This page shows the run of 2026-10-27.
How can a VM join the benchmark?
A ranked VM has a published release, a downstream user today, and passes the correctness gate. Open a pull request that adds a Docker image for the VM. Follow CONTRIBUTING.md and the image contract.