Program optimisation usually gets framed as fitting inside the compute limit. That is the floor, not the goal.
★A priority fee is compute unit price multiplied by compute unit limit. Every unit your program requires is a unit your callers pay for on every transaction, at whatever the market rate is that minute.★
The Arithmetic That Makes This Matter
priority fee = CU price × CU limit
A caller must request a limit high enough for your program to complete. ★If your instruction needs 120,000 units instead of 60,000, they pay twice the priority fee for the same trade — and during congestion, when the price per unit spikes, that gap widens in absolute terms.★
This is why compute optimisation is a user-facing decision rather than an internal one. Your program's efficiency shows up in someone else's fee line, and they cannot fix it from their side.
Where the Units Actually Go
Measuring beats guessing, and the runtime will tell you directly:
use solana_program::log::sol_log_compute_units;
sol_log_compute_units(); // ★before★
do_the_expensive_thing()?;
sol_log_compute_units(); // ★after★
The typical distribution surprises people:
| Cost | Rough weight |
|---|---|
| ★Account deserialization★ | ★Often the largest single item★ |
| ★CPI overhead★ | ★Substantial per call★ |
| Arithmetic | Usually negligible |
★msg! logging★ |
★Real, and easy to forget★ |
| Account validation | Moderate, adds up |
★The two starred at the top are where the wins are.★ Micro-optimising arithmetic while deserializing five accounts you never read is optimising the wrong end.
If your program is lean and transactions still miss, a free BoltTx key is one line for your users to test the submission path.
Deserialization Is the First Place to Look
Anchor deserializes every account in your context before your instruction body runs, whether you use it or not:
#[derive(Accounts)]
pub struct Swap<'info> {
pub pool: Account<'info, Pool>, // ★deserialized★
pub metadata: Account<'info, Metadata>, // ★deserialized even if unused★
/// CHECK: only the address is needed
pub reference: UncheckedAccount<'info>, // ★not deserialized★
}
★An account you only need the address of should not be a typed Account.★ UncheckedAccount with your own address check costs a comparison instead of a full deserialization.
For large accounts where you need one field, zero-copy avoids materialising the whole struct:
pub pool: AccountLoader<'info, LargePool>, // ★borrow, do not deserialize★
The tradeoff is real: UncheckedAccount moves validation from the framework to you, and forgetting a check is a security bug rather than a performance one. ★Use it where the account genuinely needs no validation beyond its address.★
Logging Costs More Than It Looks
msg!("swap: user={} amount={}", ctx.accounts.user.key(), amount); // ★formats + writes★
String formatting inside a program consumes units, and it does so on every single invocation — including the thousands where nobody reads the log.
★Log what you need to debug a failure, not what you would like to see when things work.★ The transaction meta already records balance changes, so logging amounts duplicates information the chain provides for free.
CPI Is Not Free
Each cross-program invocation carries setup cost beyond whatever the called program spends:
// ★Two CPIs: two lots of overhead.★
token::transfer(ctx_a, amount_a)?;
token::transfer(ctx_b, amount_b)?;
Batch where the interface allows it, and question whether an intermediate CPI is doing anything you could compute directly. ★A wrapper program that exists for organisational tidiness costs your users on every transaction.★
Depth matters too: your CPI occupies one of the four nested levels, and callers routing through an aggregator have already spent two or three.
Publish Your Numbers
★This is the part almost no program does, and it is the highest-value thing for your callers.★
swap_exact_in ~48,000 CU
add_liquidity ~62,000 CU
close_position ~31,000 CU
A caller who knows your instruction costs 48,000 units sets a limit of roughly 58,000 and pays accordingly. A caller who does not know defaults to 200,000 and pays four times more than necessary — and blames the network for high fees.
Publishing measured figures costs you nothing and directly reduces what your users spend. ★It is also a signal that you have measured at all.★
What Optimisation Does Not Fix
Worth stating so effort goes to the right place:
It does not make transactions land faster. ★Compute affects the fee, not the routing.★ A lean instruction still competes for inclusion like any other.
It does not help below the noise floor. Saving 2,000 units on a 200,000-unit transaction changes very little.
It does not fix a wrong limit. ★If callers request 200,000 units for a 48,000-unit instruction, your optimisation saved them nothing★ — which is why publishing the number matters as much as reducing it.
What Landing Looks Like
Real transactions through our delivery nodes: median confirmation 336ms — under one slot.
★Compute determines what a transaction costs; routing determines when it lands.★ Both matter to your users, and they are separate problems solved in separate places.
Where BoltTx Fits
We handle submission for the people calling your program. Compute usage is decided in your program and in their compute budget instructions.
Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure, so a transaction is not observable in transit before it lands. We never modify transaction contents, which includes never altering compute budget instructions.
Your callers sign locally. We never hold funds, never sign, and never modify transaction contents. The tip travels inside the transaction, paid on chain from their own wallet, and reverts with the transaction if it fails, because that is how Solana handles atomic transactions. They pay only on transactions that reach the chain.
Get a free API key. No monthly fee:
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
FAQ
Why does my Solana program's compute usage matter to users? Because a priority fee is compute unit price times the requested limit. Every unit your instruction requires is a unit callers pay for, and they cannot reduce it from their side.
How do I measure compute usage in a Solana program?
Call sol_log_compute_units before and after a section and read the difference from the logs. Measuring beats estimating, since the distribution rarely matches intuition.
What consumes the most compute in a typical program? Account deserialization and CPI overhead, usually well ahead of arithmetic. Optimising math while deserializing unused accounts is working on the wrong end.
How do I avoid deserializing accounts I do not use?
Use UncheckedAccount with your own address check where only the address matters. The tradeoff is that validation becomes your responsibility rather than the framework's.
When should I use zero-copy accounts?
For large accounts where you need only a few fields. AccountLoader borrows the data instead of materialising the whole struct, which avoids the deserialization cost entirely.
Does msg! logging cost compute? Yes, including the string formatting. It runs on every invocation regardless of whether anyone reads the output, so log what helps debug failures rather than what is nice to see.
How expensive is a CPI? It carries setup overhead beyond what the called program spends, and it occupies one of the four invocation levels. Batching where the interface allows reduces both costs.
Should I publish my program's compute costs? Yes. A caller who knows an instruction costs 48,000 units sets an appropriate limit, while one who does not defaults to 200,000 and overpays by roughly four times.
Does reducing compute make transactions land faster? No. Compute affects the fee, not the routing. A lean instruction competes for inclusion exactly like any other, so landing speed is a separate problem.
What is the compute limit per transaction? There is a per-transaction budget that all instructions share, including CPIs. Callers request a limit explicitly, and requesting more than needed raises their fee proportionally.
Why do my users complain about high fees? Possibly because they default to a 200,000-unit limit regardless of what your instruction needs. Publishing the measured figure often reduces their cost more than optimisation would.
Is arithmetic worth optimising? Rarely. Integer operations are cheap relative to deserialization and CPI. Measure before optimising, since intuition about where units go is usually wrong.
How much compute can one instruction use? Up to the transaction's requested limit, shared with every other instruction and CPI in it. A program that uses most of the budget constrains what callers can compose around it.
Does Anchor add compute overhead? Some, mainly through automatic account deserialization and validation. That is a reasonable trade for safety, and the cost is reducible where accounts genuinely need no validation.
Should I optimise before or after launching? Measure before launching so you can publish accurate figures. Optimisation itself can come later, but callers need the numbers from day one to set limits correctly.
How do callers use my published compute figures?
They set setComputeUnitLimit to your figure plus a margin, which lowers their priority fee proportionally without risking a compute-exhausted failure.
Related Reading
- Solana Compute Unit Pricing
- Solana CPI Depth Limits
- Solana Transaction Simulation
- Solana Account Size Planning
- Solana Transaction Landing