Skip to content

perf(precompiles): shrink per-call zone precompile lookup state - #1386

Closed
mattsse wants to merge 1 commit into
mainfrom
mattsse/perf-precompile-lookup-alloc
Closed

perf(precompiles): shrink per-call zone precompile lookup state#1386
mattsse wants to merge 1 commit into
mainfrom
mattsse/perf-precompile-lookup-alloc

Conversation

@mattsse

@mattsse mattsse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

alloy-evm runs the dynamic precompile lookup on every call frame that misses the static map, so each CALL into a zone precompile builds a fresh DynPrecompile. Registering the addresses in the map is not an option, since revm warms warm_addresses at the start of every transaction and that would change CALL gas; a new test pins that the lookup leaves the warm set untouched.

ZonePrecompileEnv carried a full CfgEnv clone although the wrappers only read spec, enable_amsterdam_eip8037 and gas_params, and L1State spread its state over three refcounted fields plus a provider clone. Both now sit behind a single Rc, and building the env before taking the precompiles out of the EVM removes a second CfgEnv clone per create_evm.

Measured with a throwaway interleaved A/B microbenchmark (release, minimum over 2000 rounds, A/A control at 0.00 ns): cloning L1State 7.2 → 4.2 ns, cloning ZonePrecompileEnv 5.7 → 4.4 ns. The remaining ~36 ns of PrecompilesMap::get is the boxed precompile the lookup API requires; caching per address was tried and measured 6 ns slower. This is a few nanoseconds out of roughly 750 per call, so the value is a minimal per-call path and the pinned warm-address invariant, not a visible speedup.

alloy-evm invokes the dynamic precompile lookup on every call frame whose
bytecode address misses the static map, so the zone builds a fresh
DynPrecompile for each CALL into a zone precompile. Registering those
addresses in the map instead is not an option: revm warms
PrecompileProvider::warm_addresses at the start of every transaction, so
that would turn cold CALLs into warm ones and change consensus gas. A unit
test now pins that the lookup leaves the warm address set untouched.

ZonePrecompileEnv carried a full CfgEnv clone even though the wrappers only
read spec, enable_amsterdam_eip8037 and gas_params, and L1State spread its
transaction-local state over three separately reference-counted fields.
Both now sit behind a single Rc, so a lookup clones one refcount instead of
copying a CfgEnv and bumping four counters. Building the env before taking
the precompiles out of the EVM also drops the second CfgEnv clone per
create_evm.

Measured with a throwaway interleaved A/B microbenchmark (release,
opt-level 3, old and new types running in one process, minimum of 2000
rounds of 2000 iterations): cloning L1State 7.2 -> 4.2 ns, cloning
ZonePrecompileEnv 5.7 -> 4.4 ns. The remaining ~36 ns of
PrecompilesMap::get is the Box<dyn Precompile> allocation the lookup API
requires; caching the constructed precompile per address measured 6 ns
slower, because the lookup must still hand back an owned DynPrecompile.
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.

1 participant