Skip to content

Conversation

@7layermagik
Copy link

Summary

Reduces per-transaction memory allocations by eliminating redundant method calls and replacing O(n) slice iterations with O(1) map lookups.

Key optimizations:

  • Cache GetProgramIDs() once per transaction (~0.82 GB savings estimated)
  • Replace slices.Contains() with precomputed map lookups in hot paths
  • Add capacity hints to frequently-allocated maps to reduce rehashing
  • Centralize NewReservedAcctsSet to avoid drift between packages

Changes

1. GetProgramIDs Caching

Problem: GetProgramIDs() was being called inside isWritable() — once per account per transaction. For a typical tx with 10 accounts, this meant 10 redundant slice allocations.

Solution: Build programIDSet map once per transaction, pass to isWritable():

// Before: O(n) allocation per account
func isWritable(tx *solana.Transaction, am *solana.AccountMeta, ...) bool {
    programIds, _ := tx.GetProgramIDs()  // Called N times per tx!
    if slices.Contains(programIds, am.PublicKey) { ... }
}

// After: O(1) lookup, map built once per tx
func isWritable(am *solana.AccountMeta, ..., programIDSet map[solana.PublicKey]struct{}) bool {
    if _, isProgramID := programIDSet[am.PublicKey]; isProgramID { ... }
}

2. slices.Contains → Map Lookups

Location Before After
isWritable() reserved accounts O(n) slice search O(1) map lookup
isWritable() program IDs O(n) slice search O(1) map lookup
recordStakeAndVoteAccounts() writable check O(n*m) nested loop O(1) map lookup
sealevel.IsWritable() O(n) slice search O(1) map lookup

3. Capacity Hints

Added capacity hints to hot-path maps to reduce GC pressure:

File Map Capacity
accounts.go instructionAcctPubkeys len(tx.Message.AccountKeys)
accounts.go validatedLoaders 4 (usually ≤4 loaders)
transaction.go ModifiedVoteStates 8
topsort_planner.go pkToAcct len(b.TxMetas)*4
block.go alreadyAdded len(slotCtx.WritableAccts)

4. Code Consolidation

Exported NewReservedAcctsSet from pkg/sealevel so both transaction.go and sealevel.go use the same set, preventing drift.

Files Changed

  • pkg/replay/transaction.go — Main isWritable changes, programIDSet, writablePubkeySet
  • pkg/sealevel/sealevel.go — IsWritable signature, exported NewReservedAcctsSet
  • pkg/rent/rent.go — Updated to use programIDSet
  • pkg/replay/accounts.go — Capacity hints
  • pkg/replay/topsort_planner.go — Capacity hint
  • pkg/replay/block.go — Capacity hint

Test Plan

  • go build ./... passes
  • go test ./pkg/replay/... ./pkg/sealevel/... ./pkg/rent/... passes
  • Run mithril on mainnet — bank hashes match (no behavioral change)
  • Monitor alloc MB/s in 100-slot summary — should decrease

Expected Impact

Based on pprof analysis:

  • ~0.82 GB reduction from GetProgramIDs caching
  • ~0.34 GB reduction from AccountMetaList reuse (via AcctMetas)
  • Improved CPU from O(1) lookups replacing O(n) iterations

🤖 Generated with Claude Code

@7layermagik 7layermagik force-pushed the perf/simd186-account-memoization branch from d45ea13 to 56ab9ea Compare January 18, 2026 04:27
7layermagik and others added 3 commits January 17, 2026 23:58
…ateTxAccts

When SIMD-186 is active, loadAndValidateTxAcctsSimd186 was loading each
account twice: once for size accumulation (Pass 1) and once for building
TransactionAccounts (Pass 2). Each GetAccount call clones the account,
causing 2x allocations and data copies per account per transaction.

Changes:
- Add acctCache slice to store accounts from Pass 1
- Reuse cached accounts in Pass 2 instead of re-cloning
- Replace programIdIdxs slice with isProgramIdx boolean mask for O(1) lookup
  (eliminates slices.Contains linear scan in hot loop)
- Reuse cache in program validation loop via tx.Message.Instructions index

Impact: ~50% reduction in account allocations/copies per transaction,
reduced GC pressure during high-throughput replay.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Cache GetProgramIDs() once per tx, build programIDSet for O(1) lookup
- Convert newReservedAccts slice to map for O(1) lookup in isWritable
- Build writablePubkeySet for O(1) lookup in recordStakeAndVoteAccounts
- Reuse AcctMetas from TransactionContext (avoid second AccountMetaList call)
- Add capacity hints to 6 hot-path maps to reduce rehashing

Expected savings: ~1GB+ allocations per profile window from
GetProgramIDs caching alone, plus CPU wins from O(1) lookups.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…sSet

- Restore panic on GetProgramIDs error in rent.go and transaction.go
  (fixes behavior regression where errors were silently ignored)
- Export NewReservedAcctsSet from sealevel package and use it in
  transaction.go (removes duplicate definition that could drift)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@7layermagik 7layermagik force-pushed the 7layer/perf/tx-alloc-reduction branch from 56f92a7 to 2d16516 Compare January 18, 2026 05:59
@7layermagik 7layermagik changed the base branch from perf/simd186-account-memoization to dev January 18, 2026 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants