All articles
Smart Contracts·January 16, 2026·5 min read

Compute Unit Budgeting for Solana HFT Programs

Every microsecond of compute you waste is a slot you lose. This guide breaks down how to request, measure, and trim compute units in Rust programs so your on-chain logic fits inside the 1.4 M CU limit without triggering ComputeBudgetExceeded errors.

Solana's runtime charges compute units (CUs) for every operation your program executes — arithmetic, account loads, cross-program invocations, syscalls. The per-transaction budget caps at 1,400,000 CU, and if you cross it mid-instruction the runtime rolls back the entire transaction with ComputeBudgetExceeded. For an HFT bot wiring into TierZero's execution layer, this isn't theoretical: a bloated arbitrage instruction that consumes 900 k CU on a quiet devnet can blow past the cap the moment mainnet adds a DEX CPI or an extra account lookup.

How the Compute Budget Instruction Works

Before your program logic runs, you prepend a ComputeBudgetInstruction to the transaction. There are two levers:

  • set_compute_unit_limit(units: u32) — reserves up to units CU for the transaction. Validators use this to schedule and prioritize work.
  • set_compute_unit_price(micro_lamports: u64) — sets your priority fee per CU consumed. Higher price means better placement in the leader's queue.

Both must appear before any other instruction. The runtime enforces the limit strictly: if set_compute_unit_limit is absent, the default is 200,000 CU per instruction (capped at 1.4 M total). For a multi-instruction transaction doing a flash-arb across two AMMs, the default is almost never enough.

A typical budget preamble in Rust client code looks like:

use solana_sdk::compute_budget::ComputeBudgetInstruction;

let cu_limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(600_000);
let cu_price_ix = ComputeBudgetInstruction::set_compute_unit_price(50_000); // micro-lamports per CU

Set the limit to what you actually need plus a small buffer (10–15%), not the theoretical maximum. Validators rank transactions partly on fee-per-CU; padding to 1.4 M when you burn 400 k makes your economics worse and your queue position worse.

Measuring Real CU Consumption

The only reliable source of truth is simulateTransaction with replaceRecentBlockhash: true and commitment: "processed". The response includes unitsConsumed in the metadata. Run this against mainnet-beta with a recent blockhash — devnet CU profiles differ because account state differs.

For on-chain profiling during development, the sol_log_compute_units() syscall emits the remaining CU count to program logs at any point you call it:

use solana_program::log::sol_log_compute_units;

pub fn process_instruction(/* ... */) -> ProgramResult {
    sol_log_compute_units(); // baseline remaining
    // ... your logic ...
    sol_log_compute_units(); // remaining after core logic
    Ok(())
}

Diff the two values to isolate exactly how many CUs your hot path costs. Do this at every major block — account deserialization, math, CPI calls — before you optimize blindly. CPI calls are the usual culprit: a single CPI into a DEX like Orca or Raydium costs roughly 1,000–2,000 CU overhead just for the dispatch, on top of whatever the callee burns.

Where CUs Actually Go in an Arb Instruction

For a two-hop swap (Token A → B → C) through two AMMs via CPI, a realistic breakdown on mainnet looks like this:

Operation Approximate CU cost
Account deserialization (8 accounts) 8,000–16,000
AMM math (curve calculation, both hops) 10,000–25,000
CPI dispatch overhead (×2) 4,000
Each AMM's internal logic 40,000–120,000 each
Token transfers (SPL token CPI ×4) 20,000–30,000
Total ~150,000–320,000

The AMM's internal logic dominates, and you cannot shrink it — it runs in their program, not yours. What you can control is your wrapper: deserialization strategy, math shortcuts, and minimizing account loads.

Rust-Level Optimizations That Actually Move the Needle

Avoid try_from_slice on large account data. borsh::try_from_slice deserializes the entire buffer. If you only need two fields from a 500-byte account struct, use a zero-copy approach with bytemuck or manually parse the byte offsets. This regularly saves 5,000–15,000 CU per account.

Use integer math over floating-point. The Solana BPF VM has no native FPU; floats are emulated and expensive. Fixed-point arithmetic with Q64.64 or Q32.32 is both faster and deterministic.

Eliminate heap allocations in hot paths. Vec::new(), String::from(), and similar calls in BPF cost CUs for the allocator. Use stack-allocated arrays or reuse pre-allocated buffers passed through account data when possible.

Batch account loads with ALTs (Address Lookup Tables). Each additional account in a v0 transaction costs 25 CU to load regardless of whether you access it. Auditing your account list and removing unused accounts is free CU savings.

Setting the Priority Fee Correctly

Priority fee strategy is inseparable from CU budgeting. Your effective fee is price_micro_lamports × units_consumed / 1_000_000 lamports. If you over-request CUs, you either pay more than necessary or you set a lower unit price to hit the same absolute fee — both outcomes hurt.

The current competitive range for Solana mainnet-beta during active market conditions sits between 10,000 and 500,000 micro-lamports per CU depending on block congestion. Track recent priority fees with getRecentPrioritizationFees and adjust dynamically rather than hardcoding. A static 50 k micro-lamport price that worked last month will get you dropped from blocks during a high-volatility session.

For latency-sensitive positions, some teams run a fee oracle that samples the 75th-percentile fee from the last 20 slots and applies a 1.2× multiplier. Crude, but it keeps you in the block without overreacting to momentary spikes.

Testing Before You Deploy

Simulation catches budget overruns; it does not catch logic errors that surface only at full load. Before promoting an instruction to mainnet:

  1. Simulate against a mainnet-beta snapshot with realistic account state — use solana-ledger-tool or a paid RPC with simulateTransaction.
  2. Add a 20% CU buffer over the simulated unitsConsumed in your limit instruction.
  3. Log CU checkpoints in your Rust program during a single testnet run with real AMM state loaded, not mocked.
  4. Monitor ComputeBudgetExceeded counts in your RPC error logs for the first 48 hours on mainnet.

Simulate obsessively, buffer conservatively, and tighten the limit once you have a week of mainnet data showing stable consumption.


If you want an on-chain program reviewed for CU efficiency or a full HFT pipeline built and operated for you, reach out to the team.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#solana#smart-contracts#hft#compute-units#rust#trading-bots