Repository navigation
perf: cache signature hashes by normalized signature - #570
Merged
Merged
Conversation
`AbiItem.getSignatureHash` keccak-hashed the signature on every call for ABI items without a precomputed `hash`. Selector lookups call it for every item they compare: `AbiItem.fromAbi` with a selector, `AbiFunction.decodeData` with an ABI, and `AbiEvent.decodeLog` and `AbiEvent.extractLogs` for each log. Memoize the hash in a bounded (8192-entry) map keyed by the normalized signature string. The key is the signature itself rather than the ABI item object, so a mutated or re-created item can never read a stale hash. The map sits next to the checksum cache in a new `internal/caches.ts`, so `Caches.clear()` resets it, and so does every `Engine.set`/`reset`/`with` call (they already call `Caches.clear`). Only `Caches.checksum` stays public.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Published pnpm add ox@pr-570
pnpm add ox@0.0.0-pr-570-c351af5 |
Merged
gakonst
added a commit
that referenced
this pull request
Nov 3, 2026
Backports #569 and #570 from main. #569: Regex literals inside a function body allocate a fresh RegExp on every evaluation. Move the ones on hot validation paths to module scope: `Hex.assert` (strict), `Value.format`/`Value.from`, `TypedData` type dependency and field validation, `Siwe.isUri`, frame nonce RPC decoding, and the integer and fixed-bytes checks in ABI overload resolution. All of them are non-global and non-sticky, so sharing them holds no `lastIndex` state. `isArgOfType` also reads the integer bit size from the integer type regex capture instead of running a second `replace` regex. 0.x has no `ox/zod` entrypoint, and keeps its own `Value.from` decimal pattern, so behavior is unchanged. #570: `AbiItem.getSignatureHash` keccak-hashed the signature on every call for ABI items without a precomputed `hash`. Memoize the hash in a bounded (8192-entry) LRU map keyed by the normalized signature string, next to the checksum cache in a new `internal/caches.ts`, so `Caches.clear()` resets it. Only `Caches.checksum` stays public. 0.x has no `Engine`, so keccak256 cannot be swapped and the engine test is not ported.
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AbiItem.getSignatureHashkeccak-hashes the signature on every call for ABI items that have no precomputedhash(raw JSON ABIs, or items created withprepare: false). Selector lookups call it for every item they compare against:AbiItem.fromAbi(abi, selector)AbiFunction.decodeData(abi, data)AbiEvent.decodeLogandAbiEvent.extractLogs, once per logThat is one keccak for each ABI item that comes before the match. This is the same cost viem removed in wevm/viem#5392.
Change
getSignatureHashnow caches the hash in a bounded map with 8192 entries, keyed by the normalized signature string (for exampleownerOf(uint256)). A hit skipsHex.fromStringandkeccak256. The key is the signature string, not the ABI item object, so a mutated or re-created item can't read a stale hash, and equal signatures share one entry.src/core/internal/caches.ts, next to the existing checksum cache.Caches.clear()resets both.Engine.set,Engine.resetandEngine.withalready callCaches.clear(), so a swapped keccak engine never sees hashes computed by a different engine.Caches.checksumremains public, so the exports ofCachesare unchanged and there are no public API or type changes.Engine.with;Caches.clear()empties the signature cache.Measurements
Node 22, CPU time, min of 5 runs,
mainvs this branch. These are steady-state numbers, where the same ABI is used repeatedly:AbiItem.fromAbi(abi200, selector)(last of 180 functions)AbiItem.getSelector(unprepared item)AbiFunction.decodeData(erc20Abi, data)AbiEvent.extractLogs(erc20Abi, 100 logs)The remaining cost of a lookup is
formatAbiItemandnormalizeSignature, which still run on every call because the key comes from them. A cache miss costs one extraMaplookup and insert.Verification
pnpm check:types(tsc -b) passed.vp check(format and type-aware lint) passed on the changed files.SKIP_GLOBAL_SETUP=1 pnpm test --project corepassed forAbiItem,Caches,Engine,AbiFunction,Abi,Address,TypedDataandindex.AbiEvent,AbiConstructorandAbiErrorfailed to connect (ECONNREFUSED 127.0.0.1:8545). CI runs them with Anvil.vp check --fixdoesn't finish on this loaded machine. I ran the same check on the changed files.Follow-up to #569.