Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Compute-unit performance

Pina tracks four performance signals on every pull request:

  • The ignored Surfpool suite records compute units for every exercised example instruction against copied base and head ELFs. Focused Mollusk fixtures cover security-sensitive behavior changes that need exact outcome checks.
  • pina profile records static whole-program estimates, binary size, text size, and syscall counts for every top-level example program.
  • Hyperfine compares representative base and head CLI commands.
  • The existing host benchmark suite compares medians for performance-sensitive core operations.

The comparison uses base - head. A positive score is an improvement because the head consumes fewer compute units. A negative score is a regression.

The jobs run in parallel and update one sticky pull-request comment. Each section keeps its emoji summary visible and collapses the full table in a details block. CLI and host timings are advisory because hosted-runner timing is noisy. Instruction-CU regressions enforce the configured policy.

PinaPod v0.2 migration results

The following exact results compare Pina v0.14.0 (eeaeb1ec) with the PinaPod v0.2 migration. Both sides use Solana’s cargo build-sbf, the pinned nightly-2025-11-20 toolchain, Mollusk 0.14.0, identical instruction bytes, and identical account fixtures.

Instruction caseBase CUHead CUPerformance changeChange
account_realloc_program/grow_0_to_88,3786,749+1,629+19.4%
account_realloc_program/initialize9,7589,782-24-0.2%
account_realloc_program/rewrite_8_to_86,9635,335+1,628+23.4%
account_realloc_program/shrink_8_to_07,1105,508+1,602+22.5%
counter_program/increment2,1841,958+226+10.3%
counter_program/initialize14,29814,198+100+0.7%
profile_program/add_tag2,2672,271-4-0.2%
profile_program/initialize7,2247,392-168-2.3%
profile_program/remove_tag2,2962,276+20+0.9%
profile_program/update_profile2,5762,554+22+0.9%

Seven of ten instruction cases improve. Compact realloc operations improve by 19.4% to 23.4% after removing duplicate account and PDA validation. The generated load_pda_mut helper makes the counter mutation 10.3% faster and prevents recursively validating a fixed representation several times at one boundary.

Three reviewed increases remained in that historical comparison. Compact initialization added 24 CU, fixed profile tag insertion added 4 CU, and fixed profile creation added 168 CU. Profile creation deliberately validated the completed String, Vec, and Option representation after its initializer ran. That final check prevented a closure from committing malformed state.

Token loader consolidation results

The focused token-loader fixture compares the former unsuffixed API with the consolidated checked API. Mint and ordinary token-account loading remain exactly unchanged because both versions use the same checked upstream account-view parsers. Canonical ATA loading pays for two additional stored-state comparisons:

Instruction caseBase CUHead CUPerformance changeOutcome
Legacy mint, success8484+0success -> success
Legacy token account, success8585+0success -> success
Token-2022 mint, success8989+0success -> success
Token-2022 token account, success8888+0success -> success
Explicit owner assertion, then load112112+0success -> success
Legacy mint, wrong owner7676+0rejected -> rejected
Legacy token account, wrong owner7676+0rejected -> rejected
Token-2022 mint, wrong owner7676+0rejected -> rejected
Token-2022 token account, wrong owner7575+0rejected -> rejected
Legacy canonical ATA, success7,6897,723-34success -> success
Token-2022 canonical ATA, success3,1903,224-34success -> success
Legacy ATA, wrong address7,6367,638-2rejected -> rejected
Legacy ATA, reassigned authority7,6897,704-15success -> rejected

The 34-CU ATA increase is 0.44% for legacy Token and 1.07% for Token-2022. It buys validation that the stored mint and current token authority agree with the inputs used to derive the ATA address. The reassigned-authority case is a deliberate semantic change, so CI labels it as a behavior change instead of claiming that the earlier rejection is a performance improvement. The three unchanged-outcome ATA increases were approved for that migration. Those historical allowances have since been retired so future pull requests preserve the improved baseline.

Static SBF profile results

The broader static profile agrees with the instruction-level measurements. Unaffected examples remain byte-for-byte stable, while every example changed by the account migration has a lower whole-program CU estimate.

ProgramBase CUHead CUPerformance changeChange
hello_solana_program837837+0+0.0%
duplicate_mutable_accounts_program1,0041,004+0+0.0%
events636636+0+0.0%
sysvar_checks_program1,6621,662+0+0.0%
system_accounts_program1,1301,130+0+0.0%
account_realloc_program5,1354,787+348+6.8%
compact_accounts_program6,8776,445+432+6.3%
counter_program4,0793,056+1,023+25.1%
profile_program4,9303,588+1,342+27.2%

The four changed binaries also shrink: account_realloc_program by 2,872 bytes, compact_accounts_program by 3,560 bytes, counter_program by 8,584 bytes, and profile_program by 11,464 bytes. These are historical migration results; the pull-request report now measures all examples automatically.

Pull request policy

Instruction cases with the same result fail on every unapproved increase. A reviewed redesign can add an exception to runtimeCuApprovals, recording the full PR baseRevision, measured base, approved maximum head, and a reason. It applies only to that base commit and measured value, then expires when the base changes. Binary growth is independently gated through binarySizeApprovals; a CU exception does not permit size growth. See CI and releases for the exception format. When base and head have different success outcomes, the report labels the case as a behavior change rather than comparing unlike execution paths as a speedup or regression. Security-sensitive cases also declare their required head outcome in runtimeExpectedOutcomes, so an accidental rejection-to-success change fails CI.

Static profiles warn when both the absolute and percentage warning thresholds are reached, and fail when both failure thresholds are reached. Smaller static increases stay visible as regressions. Programs and instructions are discovered from the head checkout. Head-only items create baselines; missing head measurements fail.

A Surfpool batch that exits nonzero fails the head measurement, with one exception: when every test binary in the batch printed test result: ok and every instruction case the batch owns has at least one sample, the exit came from the Surfpool runtime shutting down after the measurements landed. The report records that batch under incompleteTeardowns and lists it as a tolerated teardown exit instead of failing. A failed test, a binary that died before its summary, or a case without samples still fails.

The workflow uploads the reports, manifests, copied ELF files, and SHA-256 hashes as CI artifacts. This provenance prevents a successful report from silently describing a stale or different binary.

Run the comparison locally

Run the complete base-versus-head comparison from the repository root:

devenv shell -- report:cu:compare:main

Run only the current all-example static profile set:

devenv shell -- profile:cu:tracked

Reports are written under target/cu/. The Markdown report is intended for review; the JSON report is the machine-readable source for later analysis.