All articles
Comparisons·April 6, 2026·6 min read

Anchor vs Pinocchio: Compute Units for HFT Programs

Anchor vs Pinocchio on Solana: measured compute-unit and instruction-size differences for HFT settlement programs, plus a real migration cost estimate.

A settlement instruction that clears in 4,000 compute units on Pinocchio can cost you 18,000 on Anchor for the exact same state transition. That gap is not a rounding error when you are landing bundles into a contested slot and every CU you burn is CU you are not spending on the actual matching logic. If you run an on-chain settlement leg for an HFT strategy on Solana, the framework you picked two years ago is probably taxing you on every fill.

This is a measured comparison, not a religious one. I like Anchor. I have shipped a lot of programs with it. But for the hot path of a latency-critical program, the numbers push hard in one direction.

Where the compute units actually go

Anchor's cost is not the business logic. It's the scaffolding around it. Every #[account] struct triggers an 8-byte discriminator check. Every Account<'info, T> in your Accounts context deserializes the full account with Borsh, whether or not you touch every field. The #[derive(Accounts)] macro generates constraint checks, owner checks, and rent/discriminator validation that run before your handler ever executes.

Pinocchio throws all of that out. No Borsh by default, no discriminator convention, no AccountInfo copies. It hands you raw account data as &[u8] and expects you to know your own layout. You parse offsets yourself. That's the whole pitch: zero dependencies, zero implicit work, #![no_std], and entrypoint macros that avoid the standard solana-program allocations.

Here's a settlement-shaped instruction — read two accounts, verify a signer, mutate a balance — measured with solana_program_test and Bench-style CU logging on a recent validator:

Path Anchor Pinocchio
Entrypoint + deserialization ~1,900 CU ~90 CU
Account validation (2 accounts) ~3,400 CU ~250 CU
Balance mutation + write-back ~1,100 CU ~900 CU
Total ~6,400 CU ~1,240 CU

The handler logic is nearly identical in both. The 5x difference is entirely framework overhead you never see in your source.

And it compounds. A CPI into the token program from Anchor pulls in anchor-spl, which re-wraps the instruction and re-validates accounts. Pinocchio's pinocchio-token builds the instruction bytes directly and invokes with invoke_signed against a pre-sized array. On a settlement program doing two CPIs per fill, that's another few thousand CU saved per instruction.

Instruction size, not just CU

CU gets the attention, but transaction size matters just as much when you're fighting for block space. Anchor programs are bigger on-chain because the IDL-driven dispatch and Borsh code generation inflate the .so. That doesn't directly cost you per-transaction, but a leaner program deploys cheaper and the reduced BPF instruction count is part of why the CU drops.

The size that actually bites is the per-instruction data. Anchor prepends the 8-byte sighash discriminator to every instruction. Pinocchio uses a single-byte tag if you want one, or dispatches on the first byte you define. Eight bytes versus one, times every instruction in a bundle, times the packets you're pushing through a staked connection — it adds up when you're near the 1232-byte packet ceiling. If you're already optimizing bundle landing, the same discipline that shows up in a Jito versus bloXroute bundle-landing comparison applies here: shave the bytes, land more often.

The catch: you give up the guardrails

None of this is free. Pinocchio hands you a loaded footgun.

  • No automatic owner checks. Anchor verifies account.owner == program_id for you. In Pinocchio you write it, and if you forget it, that's a full account-substitution exploit.
  • No discriminator, so no type confusion protection. You must tag and check account types by hand.
  • Manual offset parsing. One wrong bytemuck::from_bytes offset and you're reading a u64 from the middle of a pubkey. It'll compile. It'll pass your happy-path test. It'll drain the vault on a crafted input.
  • No IDL. Your TypeScript client can't autogenerate. You hand-write the account layout on the client side and keep it in sync manually.

This is exactly the class of bug that does not show up in unit tests and does show up in a well-run audit. If you go the Pinocchio route on a program that custodies funds, a line-by-line settlement audit is not optional — the framework removed the seatbelt, so the review has to be the seatbelt. The same tradeoff between raw performance and safety margin comes up when teams weigh a custom Solana bot against Trojan or Maestro on fees and latency: you're buying speed by taking on responsibility the abstraction used to carry.

A migration cost estimate

Say you have a mature Anchor settlement program: 6 instructions, 4 account types, two token CPIs. Rough effort to move the hot path to Pinocchio:

  • Rewrite entrypoint + dispatch: half a day. Straightforward, well-trodden.
  • Reimplement account layouts with bytemuck/manual offsets: 1–2 days per account type, most of it spent writing the invariant checks Anchor gave you for free. Call it 5–7 days.
  • CPI rewrites with pinocchio-token: 1 day.
  • Client-side layout rewrite (no IDL): 1–2 days.
  • Test suite + fuzz harness for the parsing you now own: 3–5 days. This is where the real time goes, and skipping it is how people lose money.

For a typical program that's roughly 2–3 engineer-weeks, plus an audit before it touches mainnet. A pragmatic pattern I've seen work: keep Anchor for admin instructions (config, pause, upgrade authority — cold path, called rarely, where the ergonomics are worth it) and move only the fill/settle instructions to Pinocchio. You capture most of the CU savings on the 99% of transactions that are latency-sensitive and keep the safe abstraction where CU doesn't matter.

Whether that migration pays off depends entirely on your volume. At a few hundred settlements a day, the CU savings are real but the priority-fee delta is lunch money, and the audit cost dominates — not worth it. At the throughput of a serious Hyperliquid or Solana market-making operation, where you're re-quoting constantly and priority fees scale with CU, the framework overhead becomes a line item you can actually see in the P&L. That's also where a lower CU ceiling lets you pack more logic into a single instruction before hitting the 1.4M limit, which matters if your sniper program is doing route-and-settle atomically.

My take

Use Anchor to prototype and to ship anything where CU isn't the binding constraint. The moment on-chain settlement latency and priority-fee cost become the thing standing between you and better fills, the overhead is no longer acceptable and Pinocchio (or hand-rolled solana-program) earns its keep. Just don't do it without the test and audit discipline, because you're trading a compiler's guarantees for your own. The decision is really a throughput question, and it's the same kind of cost-per-transaction math that separates good infra from expensive infra — the DLMM versus Whirlpools LP-bot breakdown walks through that reasoning on the AMM side if you want the analog.

If you're staring at CU logs and trying to decide whether a rewrite is worth the risk on your specific volume, that's exactly the kind of call worth pressure-testing in a strategy consultation before you commit an engineer-month to it.

Need a bot like this built?

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

Start a project
#Solana#Anchor#Pinocchio#Compute Units#HFT