Benchmark every EVM opcode individually using Revm. Measures real CPU nanoseconds per opcode execution, computes gas/ns efficiency ratios, and identifies mispriced opcodes.
Ethereum gas costs are set by the protocol but don't always reflect actual computational cost. This tool measures how long each opcode takes on real hardware (via Revm v34, Cancun spec), making it easy to spot opcodes that are overpriced or underpriced relative to their peers.
The key metric is gas/ns (gas cost divided by measured nanoseconds). A high ratio means the opcode charges a lot of gas for little computation (overpriced). A low ratio means it's cheap in gas but expensive to execute (underpriced, potential DoS vector).
# Clone and build
git clone https://github.com/0xMars42/evm-opcode-bench
cd evm-opcode-bench
cargo run --release
# Run with custom sample count
cargo run --release -- -n 500
# Filter by opcode name (substring match)
cargo run --release -- -o ADD -n 1000
cargo run --release -- -o EXP # matches EXP_small, EXP_medium, EXP_large
# Single category
cargo run --release -- -c arithmetic
# CSV output
cargo run --release -- -f csv --output results/output.csv
# JSON output
cargo run --release -- -f json --output results/output.json
# Top 10 most mispriced opcodes
cargo run --release -- --top-mismatches 10
# Sort by gas efficiency ratio
cargo run --release -- --sort gas_ratio
# Multi-run for stable cold-storage measurements (median-of-medians)
cargo run --release -- --runs 5
# Verify all benchmarks execute correctly (dry run)
cargo run --release -- --verify
# List all available benchmarks
cargo run --release -- --list
# Show progress (useful for non-terminal output formats)
cargo run --release -- -f csv --output results.csv --verbose94 benchmarks across 14 categories:
| Category | Count | Examples |
|---|---|---|
| Arithmetic | 13 | ADD, MUL, DIV, EXP (small/medium/large) |
| Comparison | 6 | LT, GT, SLT, SGT, EQ, ISZERO |
| Bitwise | 8 | AND, OR, XOR, SHL, SHR, SAR |
| Stack | 8 | PUSH0, PUSH1, PUSH32, POP, DUP1/16, SWAP1/16 |
| Memory | 5 | MLOAD, MSTORE, MSTORE8, MSIZE, MCOPY |
| Storage | 8 | SLOAD warm/cold, SSTORE variants, TLOAD, TSTORE |
| Environment | 13 | ADDRESS, BALANCE, CALLER, EXTCODESIZE, SELFBALANCE |
| Block Info | 9 | BLOCKHASH, COINBASE, TIMESTAMP, BASEFEE |
| Crypto | 3 | SHA3 (32b, 256b, 1024b) |
| Control | 2 | PC, GAS |
| Logging | 5 | LOG0 through LOG4 |
| Calls | 4 | CALL/STATICCALL/DELEGATECALL warm, CALL cold |
| System | 2 | CREATE, CREATE2 |
| Precompiles | 8 | ecrecover, SHA-256, RIPEMD-160, identity, modexp, ecadd, ecmul, blake2f |
evm-opcode-bench v0.1.0
Revm v34 · Cancun spec · 2026-02-20 17:48 UTC
AMD Ryzen 7 7800X3D 8-Core Processor (16 cores) · 31.7 GB RAM · Windows 11 Pro
Opcode Mean Median P95 Gas Gas/ns Pricing
──────────────────────────────────────────────────────────────────────
▸ Arithmetic (13 benchmarks)
SUB 9.1ns 7.4ns 18.1ns 3 0.330 ·····
ADD 12.4ns 9.1ns 29.3ns 3 0.241 ·····
MUL 14.0ns 10.1ns 33.1ns 5 0.358 ·····
DIV 21.5ns 18.9ns 39.4ns 5 0.233 ·····
EXP_small 32.8ns 25.6ns 87.2ns 60 1.829 ▓▓▓▓▓ 6.4x above mean
EXP_large 1206.0ns 1012.0ns 1914.0ns 1610 1.335 ▓▓▓▓· 4.7x above mean
...
Key findings:
▲ Highest gas/ns: EXP_small (1.8 gas/ns — 6x mean)
▼ Lowest gas/ns: DIV (0.233 gas/ns — 1.2x below mean)
Global mean: 0.286 gas/ns (IQR-filtered, 13 opcodes)
- Terminal (default, aliases:
term,pretty): colored table with pricing indicators - CSV: machine-readable, one row per opcode
- JSON: structured output with full statistics
- Markdown (alias:
md): GitHub-compatible table
Each row includes: opcode name, category, mean/median/p95 nanoseconds, gas cost, gas/ns ratio.
Loopable opcodes (most opcodes): bytecode wraps the target opcode in a JUMPDEST/JUMPI counted loop. An empty baseline loop (same structure, no opcode) is measured separately. Per-opcode time = (full_elapsed - baseline_elapsed) / loop_count.
Non-loopable opcodes (cold storage, CREATE): each sample uses a fresh EVM and CacheDB to ensure cold state. A STOP-only baseline transaction is subtracted.
- Thread pinned to core 0 (
SetThreadAffinityMaskon Windows,sched_setaffinityon Linux) - Thread priority raised (Windows:
THREAD_PRIORITY_HIGHEST, Linux:SCHED_FIFO) compiler_fence(SeqCst)before and after each timing measurement- Warmup phase before sampling (100 iterations for loopable, 20 for non-loopable)
- Baseline measured once as median (not per-sample)
- 3-sigma outlier removal on final statistics
- Default: 1000 outer samples, 10000 inner loop iterations for fast opcodes
- Multi-run mode (
--runs N): runs N independent benchmark passes and takes the median-of-medians for each opcode. This eliminates run-to-run variance for cold storage and other noisy benchmarks. Use--runs 3or--runs 5for publication-quality results. - Verify mode (
--verify): dry-runs every benchmark once and checks that the EVM execution succeeds without reverting. Use this to validate benchmark correctness after modifying opcode definitions. - IQR-filtered global mean: the summary's gas/ns mean excludes statistical outliers (beyond 1.5× IQR), so precompiles like ecrecover don't distort the comparison.
-
Stack opcodes (POP, DUP, SWAP): measured with their required PUSH setup. The reported time includes stack preparation overhead. This is inherent to EVM benchmarking since these opcodes can't execute without stack inputs.
-
Composite benchmarks (SHA3, LOG, MCOPY, precompiles): loop body includes an MSTORE to initialize memory before the target opcode. The baseline loop doesn't include this MSTORE, so a few nanoseconds of setup cost are included in the measurement.
-
Cold storage (SLOAD_cold, SSTORE variants): non-loopable strategy means each sample allocates a fresh CacheDB. Allocation noise causes higher variance (30-60% between runs) compared to loopable opcodes (<5%).
-
ecrecover is significantly slower than other precompiles (~150us vs ~0.3-3us) due to full ECDSA signature recovery. Its gas cost (3000) gives it the lowest gas/ns ratio of any benchmark.
Works on Windows and Linux (including WSL). Thread pinning and priority use platform-specific APIs with graceful fallback.
Requires Rust 1.83+.
MIT