Skip to content

blockifier: pre-reserve capacity when rebuilding trimmed state maps - #14937

Open
gkaempfer wants to merge 1 commit into
mainfrom
claude/perf/blockifier-intersect-capacity-73204
Open

blockifier: pre-reserve capacity when rebuilding trimmed state maps#14937
gkaempfer wants to merge 1 commit into
mainfrom
claude/perf/blockifier-intersect-capacity-73204

Conversation

@gkaempfer

Copy link
Copy Markdown
Contributor

Summary

Follow-up performance optimization to #14625.

StateMaps::trim_to_accessed_keys rebuilds each HashMap via .collect() on a filter_map iterator over the accessed-keys BTreeSet. filter_map's size hint has a lower bound of 0 (since the filter can drop any number of elements), so collect()'s internal reserve() call is a no-op — the resulting HashMap grows through repeated reallocation and rehashing as elements are inserted instead of being sized once up front.

This PR pre-reserves capacity for min(keys.len(), map.len()) before extending, which is a tight upper bound on the final size: accessed keys are (almost always) a subset of the map being intersected, since get_os_initial_reads already force-reads the nonce/class-hash/compiled-class-hash of every contract that ends up in accessed_keys.

Why this matters: trim_to_accessed_keys runs once per block in apollo_batcher's hot path. Avoiding the incremental grow-and-rehash cycle removes a constant-factor overhead from a function whose cost already scales with block size.

Test plan

  • cargo build -p blockifier
  • cargo clippy -p blockifier --lib (clean)
  • SEED=0 cargo test -p blockifier --lib state::cached_state (43 passed)
  • scripts/rust_fmt.sh (no diff beyond the change)
  • Reviewed by an Opus subagent for correctness and style; two nits addressed (tighter capacity bound, comment length)

Generated by Claude Code

trim_to_accessed_keys() rebuilds each StateMaps HashMap via
collect() on a filter_map iterator, whose lower size-hint bound is
0. That forces incremental reallocation and rehashing as entries
are inserted. Reserve capacity for accessed_keys.len() up front
since accessed keys are expected to almost always be present in the
map being intersected.

Follow-up to #14625.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzWvNVLye1Jcrh1u2Pu5hd
@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Pure allocation/performance tweak with identical intersection semantics; no auth, state correctness, or API changes.

Overview
Performance follow-up to earlier blockifier caching work: StateMaps::trim_to_accessed_keys still rebuilds each map by intersecting with accessed keys, but the inner intersect helper no longer uses collect() on a filter_map alone.

Because filter_map reports a size hint lower bound of 0, HashMap::collect() did not pre-size the result and the map grew via repeated reallocations. The change builds the map with HashMap::with_capacity(keys.len().min(map.len())) and extends the filtered pairs, matching the tight upper bound on how many entries can survive the intersection.

Behavior is unchanged; this targets per-block overhead on the batcher path where trim_to_accessed_keys runs often.

Reviewed by Cursor Bugbot for commit 305e3ab. Bugbot is set up for automated code reviews on this repo. Configure here.

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

@gkaempfer gkaempfer self-assigned this Aug 10, 2026
@github-actions

Copy link
Copy Markdown

Artifacts upload workflows:

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.

3 participants