Skip to content

perf: reduce allocations in lean block import and head computation#1447

Open
zemse wants to merge 2 commits into
ReamLabs:masterfrom
zemse:perf/lean-fork-choice-alloc
Open

perf: reduce allocations in lean block import and head computation#1447
zemse wants to merge 2 commits into
ReamLabs:masterfrom
zemse:perf/lean-fork-choice-alloc

Conversation

@zemse

@zemse zemse commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What was wrong?

Two hot paths in the lean node allocated far more than necessary on every block and tick:

  • update_head and produce_block_with_signatures iterated the entire
    latest_known_aggregated_payloads table and SSZ-decoded every Vec<PayloadProof>
    (the large post-quantum signatures) on each call. The only consumer reads just the
    validator id and attestation data root from each key, so the proof values were
    decoded and thrown away.
  • on_block cloned the whole SignedBlock, carrying those same large signatures,
    just to hand it to the block store, which then cloned it again for its cache.

How was it fixed?

  • Added iter_keys() and entry_count() to the payloads table so callers read the
    keys and the entry count without decoding the proof values, and changed
    extract_attestations_from_aggregated_payloads to take &[SignatureKey]. The
    previous is_empty() skip is preserved by construction: both insert sites only ever
    store non-empty proof lists.
  • Added LeanBlockTable::insert_ref so on_block passes the block by reference and
    redb serializes it in place; the owned insert now delegates to it.

No behavior change. Measured on a single-node devnet-4 (60s): produce_block_with_signatures
~22 MB -> ~40 KB per call, update_head ~15 MB -> ~4 MB per call, total process
allocation churn ~8.4 GB -> ~7.3 GB.

To-Do

@zemse zemse marked this pull request as draft June 18, 2026 21:52
zemse added 2 commits June 19, 2026 03:40
update_head and produce_block_with_signatures iterated the entire
latest_known_aggregated_payloads table and SSZ-decoded every
Vec<PayloadProof> on each call, but the only consumer reads just the
validator id and attestation data root from the key. The proof values
were never used.

Add iter_keys and entry_count to the table so callers read keys and the
entry count without decoding the (large) proof payloads, and take the keys
in extract_attestations_from_aggregated_payloads. Keys are only ever stored
with a non-empty proof list, so the previous emptiness check is preserved
by construction.
The block table's insert took the SignedBlock by value, so on_block cloned
the whole block (carrying the large post-quantum signatures) to hand it
over, and the table then cloned it again for its cache. Add
LeanBlockTable::insert_ref so on_block passes the block by reference and the
value is serialized in place. The owned insert now delegates to it, leaving
one clone only when the cache is enabled.
@zemse zemse force-pushed the perf/lean-fork-choice-alloc branch from 0cff069 to 45dcb21 Compare June 18, 2026 22:11
@zemse zemse marked this pull request as ready for review June 18, 2026 22:18
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