starknet_os: preallocate CompressionSet vectors to avoid reallocation - #15003
starknet_os: preallocate CompressionSet vectors to avoid reallocation#15003gkaempfer wants to merge 2 commits into
Conversation
`decompress` runs once per block over the full state diff. Both `unique_values` and `result` grow via push/extend from `Vec::new()` even though their final lengths are already known from the decoded header (sum of unique-value bucket lengths, and data_len, respectively), causing avoidable reallocations for large state diffs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHx5w4Xp5DBX5oBBTTFEvt
Follow-up to #14779, which preallocated the two Vecs in decompress(). The sibling compress() path (CompressionSet::new, invoked once per block via the compression_hint Cairo VM hint) has the same pattern: bucket_index_per_elm and repeating_value_bucket grow from Vec::new() via push in a per-value loop, even though both are bounded by values.len() before the loop starts.
PR SummaryLow Risk Overview In In Reviewed by Cursor Bugbot for commit 5a9a419. Bugbot is set up for automated code reviews on this repo. Configure here. |
Context
Follow-up to #14779, which preallocated the two
Vecs insidestateless_compression::utils::decompressthat grew fromVec::new()even though their final length was already known from the decoded header.That PR's sibling function,
compress(), has the same pattern and is also on a production hot path:compress()is invoked from thecompression_hintCairo VM hint (crates/starknet_os/src/hints/hint_implementation/stateless_compression/implementation.rs), a hint registered incrates/starknet_os/src/hints/enum_definition.rsand executed once per block during OS/Cairo execution — not test-only code.Problem
CompressionSet::new(called fromcompress) builds twoVecs viaVec::new()+pushinside a loop overvalues: &[Felt], even though both are bounded byvalues.len()before the loop starts:bucket_index_per_elm: exactly onepushper loop iteration, so its final length is always exactlyvalues.len().repeating_value_bucket: pushed only on the "value already seen" branch, so its final length is at mostvalues.len(), never more.Growing both from capacity 0 means ~log2(N) reallocations and O(N) redundant
(usize, usize)/usizecopies per block, on a per-block hot path — the same class of inefficiency #14779 fixed on the decompress side.Fix
Preallocate both
Vecs withvalues.len()viaVec::with_capacity, mirroring the pattern already used elsewhere in this file (get_bucket_offsets,unpack_felts,unpack_felts_to, and the two call sitesdecompressalready uses from #14779).This is behavior-neutral —
Vec::with_capacitycannot panic or change output; capacity isn't observable through any of theVecAPIs used here (len,iter, slice deref).Verification
With
sequencer_venvactive andRUSTC_WRAPPER=""(sccache unavailable in this environment):cargo build -p starknet_os— clean.SEED=0 cargo test -p starknet_os stateless_compression— 35 passed, 0 failed.cargo clippy -p starknet_os --all-targets— clean.scripts/rust_fmt.sh— no diff beyond this change.An independent Opus review verified: the capacity for
bucket_index_per_elmis exact and forrepeating_value_bucketis a safe upper bound never exceeded; no new panic or observable-behavior surface; the optimization is real (not a no-op) given mainnet block sizes; and the change matches this file's existingVec::with_capacityconventions.🤖 Generated with Claude Code
Generated by Claude Code