Skip to content

perf(compiler): compare byte arrays a word at a time on SBF - #29

Draft
paulbalaji wants to merge 4 commits into
CrocSwap:mainfrom
paulbalaji:opt-byte-array-equal
Draft

paulbalaji wants to merge 4 commits into
CrocSwap:mainfrom
paulbalaji:opt-byte-array-equal

Conversation

@paulbalaji

@paulbalaji paulbalaji commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Problem

== on [N]byte compiles to an element loop that costs ~11 SBF instructions per byte, so an equal [32]byte key pair costs ~350 CU. Keys and hashes are exactly what programs compare.

Solution

For arrays of at least 8 one-byte integers (byte, uint8, int8), the SBF build of arrayN_equal XORs whole words (fixed-size __builtin_memcpy, one unaligned ldxdw per 8 bytes) plus any tail bytes and checks the fold once. Comparing bytes has no side effects, so dropping the early exit can't be observed. Struct equality picks it up through the same helper. [N]bool, wider elements, shorter arrays and host C keep the element loop.

Benchmarks

Whole transaction against main 939b215, equal inputs, same on both profiles:

arrays main this PR
[32]byte, plus a struct holding one 979 183
[64]byte 1,053 207
[32]int8 1,165 739
[8]byte 165 162

About 400 CU saved per equal [32]byte pair, the same as before #25. The totals are lower than on 79cf25c because #22 turned the test's own copies into syscalls. The Hyperlane Go programs had already moved their key checks to an unrolled XOR, so there the effect is small: measured before #25, up to 1.6% less total CU per program, 584/584 tests pass.

Tests

  • TestByteArrayEqualSBF on fogo-v0 and solana-v3: nine array types (byte at five lengths, int8 at two, plus [32]bool and [8]uint16), equal and with bit 0 and bit 7 flipped at every position, 437 comparisons equal to Go.
  • TestByteArrayEqualC: only [32]byte gets the word compare in a program that also compares [7]byte, [32]bool and [8]uint16.
  • TestCorpusMatchesPreCheckCompiler keeps only the #else loop (and strips perf(compiler): lower bulk copies and zeroing to sol_memmove_ and sol_memset_ #22's memory syscall blocks) and matches every recorded hash.
  • make test: same failures as main 939b215 on this machine (stdlib provenance/features tests with Go 1.23.2, TestFullSwapFacts under /tmp).
  • verify_rebuild.py + check_rebuild_reference.py pass locally: the full-swap reference ELFs change (clock2 doesn't), so results/compiler/2026-10-19-byte-array-equal/rebuild/metrics.json is the new default reference.

Linked work

Closes #26, together with #27, #28, #30 and #31. If this merges first, #26 should stay open until the rest land.

Rebased onto main after #25. Same family as #5 and #22 (byte loops on SBF); #31 does the same for hand-written compare loops. Used by hyperlane-xyz/hyperlane-monorepo#9852 through hyperlane-integration-4.

== on [N]byte/[N]uint8/[N]int8 compiled to a byte loop of ~11 SBF
instructions per byte, so a [32]byte key comparison cost ~350 CU. For
N >= 8 the SBF build now folds the XOR of whole words (fixed-size
__builtin_memcpy, one unaligned ldxdw per 8 bytes) plus any tail bytes.
Byte comparisons have no side effects, so dropping the early exit is
unobservable. Host C, bool and wider elements keep the element loop.
The change moves the full-swap reference ELFs, so CI's fresh-rebuild check now compares against this branch's rebuild/metrics.json, the default reference in scripts/verify_rebuild.py.
paulbalaji added a commit to hyperlane-xyz/hyperlane-monorepo that referenced this pull request Oct 19, 2026
hyperlane-integration-4 (3ddeff0) is upstream main 939b215, which merged
our earlier PRs #9 to #24 in CrocSwap/goSVM#25, plus the open draft PRs:
whole-word runtime loads, stores and key comparisons (CrocSwap/goSVM#27),
solana.FindAddress on sol_try_find_program_address, also behind
pda.Seeds.FindAddress (CrocSwap/goSVM#28), word-at-a-time == on byte
arrays (CrocSwap/goSVM#29), the exact MinimumBalanceFor fast path at
thresholds 2.0 and 1.0 (CrocSwap/goSVM#30), and word compares in front of
hand-written byte compare loops (CrocSwap/goSVM#31). The SDK snapshot is
copied from the new CLI.
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.

Byte-at-a-time runtime and codegen add 0.3k to 4k CU to small instructions

1 participant