A transaction that fits under the compute limit can still waste priority fees,
reduce composability, and fail when a heavier state path appears in production.
Ask which work is necessary, how much each path consumes, and what limit the
client should request.
This guide gives you nine fixes and a reusable Compute Budget Review. It
separates transaction budget sizing from program optimization, because raising a
limit and removing work solve different problems.
Start with the transaction debugging guide
if you do not yet preserve simulation logs and unitsConsumed.
Fix 1: measure the right number before changing code
Solana meters program execution in compute units. Current documentation lists a
default allocation of 200000 CUs for each non-builtin instruction and a maximum
of 1400000 CUs per transaction. Builtin instructions that have not migrated to
sBPF use a different default.
Four numbers matter:
- actual units consumed, measured during simulation or execution
- requested CU limit, the ceiling set in the transaction
- CU price, expressed in micro-lamports per requested unit
- priority fee, calculated from CU price multiplied by requested CU limit
The base signature fee is separate. Reducing actual compute does not directly
reduce that base fee. It lets the client request a tighter limit, which can lower
the priority fee at the same CU price and give the transaction less scheduler
cost.
Profile the real instruction path
Begin with an instruction-level baseline. Record unitsConsumed for successful
and failing simulation paths, then measure suspicious program sections locally.
Solana's optimization guide uses the compute_fn! macro:
compute_fn!("apply settlement" => {
apply_settlement(&mut position, amount)?;
});Profile realistic account sizes and branches. An empty vector, first deposit, or
single-item order can hide the expensive path that appears after months of use.
Store the program build, test fixture, instruction name, input class, and CU
result. A number without those coordinates cannot become a regression test.
Fix 2: set the client limit from simulation
Solana recommends simulating the transaction and adding a 10% safety margin.
The limit should cover the complete instruction list, including setup, compute
budget, and cleanup instructions.
import { ComputeBudgetProgram, Transaction } from '@solana/web3.js';
const probe = new Transaction().add(
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }),
...businessInstructions,
);
probe.feePayer = payer;
probe.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
const simulation = await connection.simulateTransaction(probe);
if (simulation.value.err || !simulation.value.unitsConsumed) {
throw new Error('Cannot size compute from a failed simulation');
}
const requestedUnits = Math.min(
1_400_000,
Math.ceil(simulation.value.unitsConsumed * 1.1),
);
const finalTransaction = new Transaction().add(
ComputeBudgetProgram.setComputeUnitLimit({ units: requestedUnits }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: cuPrice }),
...businessInstructions,
);Do not silently fall back to the maximum after a failed simulation. That turns a
program or account error into an oversized paid request.
Fixes 3 and 4: remove expensive logs and right-size data
3. Keep only operational logs
String formatting, concatenation, and base58 conversion consume compute. Solana's
published example measured 11962 CUs for a formatted account-key log and 262 CUs
for the key's direct .log() method. Treat those figures as an illustration,
then measure your build.
Keep instruction names, stable error context, and values needed for incident
response. Remove prose logs and repeated account dumps from hot paths.
4. Use the smallest correct integer type
A u64 is correct for token amounts, but it is wasteful for a boolean-sized flag
or a bounded counter. Smaller types reduce memory movement and serialized account
size when the domain permits them.
Correctness comes first. Document the maximum value, unit, overflow behavior,
and migration plan before narrowing an existing field.
Fixes 5 and 6: reduce serialization and repeated PDA work
5. Avoid serializing more state than the instruction needs
Large account structs cost compute to deserialize and serialize. Split unrelated
state only when the extra accounts and coordination are worth it. For large,
frequently mutated structures, benchmark zero-copy access against normal Anchor
accounts.
Zero copy changes layout and safety obligations. It is not a blanket upgrade.
Review alignment, versioning, initialization, and every reader before adopting
it. The Solana accounts guide explains the
ownership and data boundary underneath this decision.
6. Reuse a validated PDA bump
find_program_address may try several bump values. Once a canonical PDA is
created, storing its bump lets later instructions validate with known seeds and
the recorded bump rather than searching again.
The bump is not a substitute for validation. The program must still bind the
address to the correct seeds and program ID. Review these rules in the
program security checklist.
Fixes 7 and 8: shorten CPI paths and account scope
7. Remove avoidable CPIs
A CPI has invocation overhead and transfers account privileges into another
program. Do not replace a sound protocol boundary merely to save compute, but do
remove duplicate calls, repeated parsing, and CPIs whose result the caller
already has.
Batching can help when the callee supports it. It can also make one instruction
too large or create more account contention. Measure the complete transaction,
not the CPI in isolation.
8. Load and lock only required accounts
Each extra account increases message, loading, validation, and scheduler work.
Writable accounts also constrain parallel execution. Mark read-only accounts
correctly and avoid broad shared writable state when a narrower state partition
matches the business rules.
This is both a compute and throughput decision. Changing account topology can
alter authorization and concurrency, so treat it as architecture rather than a
micro-optimization.
Fix 9: turn CU measurements into a release gate
A one-time cleanup decays as features arrive. Add CU ceilings to tests for the
important instruction families and state sizes.
const result = await connection.simulateTransaction(transaction);
if (result.value.err) throw result.value.err;
expect(result.value.unitsConsumed).toBeLessThanOrEqual(185_000);Choose the ceiling from measured behavior plus an explicit margin. When the test
fails, inspect the diff and update the budget only when the added work is
intentional. Keep separate fixtures for common, worst valid, and failure paths.
Track production percentiles by instruction version when your telemetry permits
it. A stable average can hide a growing tail.
The Compute Budget Review
Run this review before a program release:
- simulate every important instruction family and preserve unitsConsumed
- exercise realistic and worst valid account sizes
- profile the most expensive program sections with stable fixtures
- remove formatting and diagnostic logs from hot paths
- verify each stored integer is no wider than its documented domain requires
- benchmark serialization and zero-copy alternatives before changing layouts
- validate known PDA bumps instead of repeating avoidable searches
- inspect every CPI and writable account for necessary work and privileges
- set client CU limits from simulation plus a justified margin
- enforce per-path CU ceilings in CI and review regressions
The output should include the measured path, program build, requested limit,
actual consumption, margin, CU price source, and test ceiling.
FAQ, sources, and next step
Does a higher compute-unit limit make a transaction faster?
No. It gives execution a higher ceiling. Scheduling priority also depends on the
fee and cost model. Oversizing the requested limit can increase the priority fee
without reducing the program's work.
Why add 10% after simulation?
It is the current Solana documentation recommendation and provides modest room
for path variation. Use broader fixtures and measured percentiles when your
instruction can consume materially different amounts across valid state.
Does lower compute always mean lower transaction fees?
No. The base fee is separate. Lower actual usage helps when it allows a smaller
requested limit, because priority fees use the requested limit rather than the
units ultimately consumed.
Should every Anchor account use zero copy?
No. It adds layout and safety complexity. Use it where serialization is a
measured bottleneck and the maintenance tradeoff is justified.
Can RPC infrastructure optimize my program?
It can provide simulation, fee, delivery, and status evidence. It cannot remove
program work. For RPC observability and transaction delivery, see
RPC Edge. For program changes, keep the optimization loop
in code and tests.
Primary sources
- Solana compute budget
- Solana fee structure
- Solana compute optimization guide
- Solana program limitations
Read Solana transaction reliability
before changing submission and retry behavior. If the expensive path crosses
program logic, account architecture, and transaction composition,
try a Builderz trace review.



