Benchmark policy
How Hopper reports same-provenance performance data without overclaiming.
Compute-unit measurements for individual Hopper primitives on Solana.
How Benchmarks Work
Each benchmark dispatches a single Hopper operation between two
sol_log_compute_units() syscalls. The CU delta is captured from
validator transaction logs:
delta = first_remaining - second_remaining
The primitive benchmark program lives in the sibling
hopper-bench product repo.
From this framework workspace, hopper profile bench still knows how to run
the primitive lab; cross-framework orchestration, Docker runners, baselines,
and raw artifacts are owned by the benchmark repo so release docs never drift
from the executable harness.
Automation Status
The benchmark program defines instruction discriminators 0..=18. All 19
primitives are covered by the benchmark repo's Docker runner and by the host
hopper profile bench path. Release gates consume the benchmark repo's
baselines and artifacts; this framework repo keeps only lightweight fixtures
and historical result snapshots.
Release-Facing Benchmark Policy
Release-facing comparison tables in this repository must come from one
hopper-bench run that uses the same lockfile, SBF toolchain, Mollusk version,
seed set, feature flags, release profile, and command line for every included
framework.
The current vault snapshot includes Hopper, an in-tree Anza Pinocchio target,
and Quasar's upstream examples/vault target. Quasar's upstream vault exposes
only deposit and withdraw, so validation-only rows are shown as n/a for
Quasar instead of being synthesized by the harness.
CU Results
Measured on solana-test-validator 2.1 (April 2026).
| Disc | Operation | Expected CU | Category |
|---|---|---|---|
| 0 | check_signer |
~20 | Validation |
| 1 | check_writable |
~20 | Validation |
| 2 | check_owner |
~50 | Validation |
| 3 | Vault::load() (T1 full check) |
~120 | Account loading |
| 4 | check_keys_eq |
~40 | Validation |
| 5 | Vault::overlay() (57 bytes) |
~8 | Memory access (Tier A) |
| 6 | write_header |
~30 | Account init |
| 7 | zero_init (57 bytes) |
~15 | Account init |
| 8 | check_signer_fast |
~12 | Validation (fast path) |
| 9 | emit_event (32-byte payload) |
~100 | Events |
| 10 | TrustProfile::load (Strict) |
~130 | Trust loading |
| 11 | pod_from_bytes (57 bytes) |
~6 | Memory access (Tier B) |
| 12 | StateReceipt::begin + commit |
~50 | Receipts |
| 13 | read_layout_id + compare |
~15 | Fingerprint check |
| 14 | StateSnapshot::capture + diff |
~30 | State tracking |
| 15 | overlay_mut + field write |
~10 | Memory access (Tier A mut) |
| 16 | raw_cast_baseline (unsafe ptr) |
~4 | Competitor baseline |
| 17 | StateReceipt (enriched fields) |
~80 | Receipt (all fields) |
| 18 | receipt + emit (72B log) |
~150 | Receipt + event |
Memory Access Tier Comparison
| Tier | Operation | CU | What you get |
|---|---|---|---|
| Raw (unsafe) | raw ptr cast |
~4 | Size check + pointer cast only. Competitor baseline |
| B (pod) | pod_from_bytes |
~6 | Bounds-checked typed view (+2 CU) |
| A (safe) | Vault::overlay() |
~8 | Header + layout_id + bounds check (+4 CU) |
| A (mut) | overlay_mut + field set |
~10 | Mutable overlay + write (+6 CU) |
| C (raw) | load_unchecked |
~6 | No validation, caller risk |
| Full load | Vault::load() |
~120 | Owner + disc + version + layout_id + size |
| Strict trust | TrustProfile::load |
~130 | Full cross-program trust validation |
The Performance Story
Hopper's safe path is within 4 CU of raw.
A raw *const u8 as *const T pointer cast costs ~4 CU. Hopper's safe overlay
costs ~8 CU. The 4 CU difference
buys you: bounds checking, header validation, and layout_id fingerprint
verification.
Hopper's raw path exists when you need it. pod_from_bytes at ~6 CU
is 2 CU from raw, with bounds checking. load_unchecked matches raw.
For hot paths where accounts are already validated, use Tier A overlay.
For cold paths, use Vault::load() at ~120 CU for full protocol-grade
validation. The cost of safety scales with how much safety you need.
Validation Cost Breakdown
| Check | CU | Purpose |
|---|---|---|
check_signer |
~20 | Verify account is a signer |
check_signer_fast |
~12 | Optimized signer check |
check_writable |
~20 | Verify account is writable |
check_owner |
~50 | Compare owner against program_id |
check_keys_eq |
~40 | Compare two account keys |
| Full T1 load | ~120 | All checks: owner + disc + version + layout_id + size |
| Strict trust load | ~130 | TrustProfile with all validations |
Receipt and Tracking Overhead
| Operation | CU | Notes |
|---|---|---|
StateReceipt::begin + commit |
~50 | Snapshot + diff + encode to 72 bytes |
StateReceipt (enriched) |
~80 | + phase, compat_impact, validation, migration |
receipt + emit |
~150 | Full cycle: begin + set + commit + emit |
StateSnapshot::capture + diff |
~30 | Snapshot + diff without receipt framing |
read_layout_id + compare |
~15 | 8-byte fingerprint verification |
emit_event (32 bytes) |
~100 | Log-based event emission |
emit_event (128 bytes) |
~120 | Larger event payload |
A full enriched receipt (snapshot + diff + enriched fields + encode) costs ~80 CU. Emitting it as an event adds ~70 CU for the syscall. For a typical instruction budget of 200,000 CU, full receipt tracking with emission adds 0.075% overhead.
What This Means
Safe vs Raw: The Honest Comparison
Raw unsafe cast (competitor baseline): ~4 CU
pod_from_bytes (bounds-checked): ~6 CU (+2 CU)
Vault::overlay (safe, validated): ~8 CU (+4 CU)
Full Vault::load (protocol-grade): ~120 CU (30x raw)
Hopper's safe overlay is 4 CU more than raw. The full validation path is 30x more expensive, but you typically pay that cost once per instruction, then use overlays for all subsequent access.
Receipt Overhead: Negligible
Basic receipt (begin + commit): ~50 CU (0.025% of 200k budget)
Enriched receipt (all fields): ~80 CU (0.040% of 200k budget)
Receipt + emit (full audit trail): ~150 CU (0.075% of 200k budget)
A complete audit trail of every state mutation costs less than a single
check_owner call in this benchmark. Receipts are cheap enough to make the
default answer "yes" for audit-sensitive state changes, while tiny one-shot
programs can still omit them when every byte matters.
Running Benchmarks
# Primitive lab from this framework workspace
hopper profile bench
# Cross-framework parity lab from the sibling benchmark checkout
cd ../hopper-bench
./measure.sh all
The benchmark lab builds and deploys the Hopper benchmark program, provisions
deterministic fixture accounts, simulates each implemented primitive
benchmark, parses bounded sol_log_compute_units() deltas, and emits JSON/CSV
artifacts in the benchmark repo's results directory.
Golden baselines, Docker runners, competitor locks, CI thresholds, and the
long-form benchmark roadmap are maintained in the sibling hopper-bench repo.
Competitor-Shaped Baselines
| Framework Style | Equivalent CU | What It Does |
|---|---|---|
| Quasar / raw-cast | ~4 | ptr as *const T, no validation |
| Steel / podded | ~6 | Bounds-checked Pod cast |
| Hopper overlay | ~8 | Header + layout_id + bounds |
| Anchor / borsh | ~500-2000 | Deserialization + clone |
Hopper's safe path is closer to raw-cast frameworks than to Anchor. The 4 CU premium over raw buys header validation, fingerprint verification, and a clean typed API.
Framework Parity Benchmark (Vault, 8-seed average)
Measured with the sibling hopper-bench Mollusk parity harness on 2026-05-25.
Every included framework used the same deterministic user seed set, SBF
toolchain, runner, and command line. n/a means the upstream comparator does
not implement that benchmark instruction.
| Scenario | Hopper | Anza Pinocchio | Quasar |
|---|---|---|---|
| Authorize | 431 CU | 2512 CU (+2081) | n/a |
| Auth-fail (missing sig) | 72 CU | 41 CU (-31) | n/a |
| Counter (segment-safe) | 551 CU | 2539 CU (+1988) | n/a |
| Deposit | 1669 CU | 3856 CU (+2187) | 1767 CU (+98) |
| Withdraw | 453 CU | 2548 CU (+2095) | 603 CU (+150) |
| Unsigned withdraw | rejected | rejected | rejected |
| Binary size | 7.53 KiB | 7.73 KiB | 6.27 KiB |
The Pinocchio column above is built in-tree from the benchmark repo's Anza Pinocchio target, not borrowed from Quasar's reference sample or an older "Pinocchio-style" proxy number.
Benchmark provenance checklist
Every parity result published from hopper-bench must record:
- Hopper framework commit and benchmark repo commit.
- Quasar source commit or release tag.
- Pinocchio crate versions when the Pinocchio column is included.
- Rust, Solana/Agave SBF, and Mollusk versions.
- Exact feature flags and release profile.
- Exact reproduction command and seed count.
Current benchmark provenance
| Field | Value |
|---|---|
| Result files | hopper-bench/results/framework-vaults-current-head-2026-05-25/vault-framework-comparison.{json,csv} |
| Hopper framework checkout | 300797d plus local audit-fix changes |
| Benchmark checkout | 4c5183c clean |
| Quasar checkout | 5fda2f5 clean |
| SBF toolchain | cargo-build-sbf 4.0.0, platform-tools v1.53 |
| Samples | 8 deterministic user seed cases |
| Command | ./compare-framework-vaults.ps1 -HopperRoot D:\tmp\Hopper-Solana-Zero-copy-State-Framework -QuasarRoot D:\tmp\quasar -OutDir results\framework-vaults-current-head-2026-05-25 |
Performance observations
- Hopper is within 150 CU of Quasar on the two upstream Quasar vault workloads while adding Hopper's state-contract surface in its own parity target.
- Hopper is lower-CU than the in-tree Anza Pinocchio parity target on the measured PDA-bearing success paths in this vault contract. Treat that as a result for this benchmark, not a universal "faster than Pinocchio" claim.
- Hopper produces a smaller binary than the four-instruction Anza Pinocchio
parity target. Quasar remains 1.26 KiB smaller, but its upstream vault only
implements
depositandwithdraw. - Quasar's upstream vault does not implement
authorizeorcounter_access, so those rows are intentionally absent for Quasar.
Architecture and DX observations
- Verify-only PDA avoids
sol_curve_validate_pointby comparing hashes directly against the known PDA address. This is a Hopper optimization to keep measuring against same-provenance competitors, not a published Pinocchio performance claim. - The fast entrypoint receives instruction data via the second SVM register, avoiding a full-buffer account scan on supported runtimes.
- Hopper's claim is not "raw Pinocchio is slower." The claim is that Hopper packages low-overhead account access with framework validation, schema, lifecycle, CPI, and CLI tooling.
Where Pinocchio is still the right choice
Use raw Pinocchio directly when the target program should remain a minimal manual substrate with no framework-owned account lifecycle, schema, CLI, or validation layer. Hopper is the framework-layer option when those surfaces are worth carrying.
The parity vault source is at
examples/hopper-parity-vault.
The cross-framework runner lives in the sibling hopper-bench repo.
CU Budget Reference
| Scenario | Typical CU | Hopper Overhead |
|---|---|---|
| Simple transfer (1 account) | ~5,000 | ~128 CU (load + overlay + receipt) |
| DeFi swap (3 accounts) | ~50,000 | ~400 CU (3 loads + overlays + receipt) |
| Complex instruction (6 accounts) | ~150,000 | ~800 CU (6 loads + overlays + receipt) |
In all scenarios, Hopper overhead is <2% of the total instruction budget.
