Skip to content

execution/commitment: enchance MPT hashing hot-paths to reduce allocations#20796

Open
Sahil-4555 wants to merge 7 commits intoerigontech:mainfrom
Sahil-4555:opt/mpt-commitment-zero-alloc
Open

execution/commitment: enchance MPT hashing hot-paths to reduce allocations#20796
Sahil-4555 wants to merge 7 commits intoerigontech:mainfrom
Sahil-4555:opt/mpt-commitment-zero-alloc

Conversation

@Sahil-4555
Copy link
Copy Markdown
Contributor

@Sahil-4555 Sahil-4555 commented Apr 24, 2026

This PR includes targeted memory improvements to the execution/commitment MPT hashing hot-paths, resulting in considerable reductions in total allocation and execution time. The improvements are achieved by gradually replacing dynamic heap allocations with pre-allocated structural buffers. We specifically developed a zero-allocation HexToCompactBuf method and added [128]byte scratch buffers to the HexPatriciaHashed object to entirely minimize memory escapes while encoding keys during branch folding and unfolding. Furthermore, we avoided costly per-job make([]byte) allocations by embedding a [64]byte prefix array directly into the pooled DeferredBranchUpdate struct, ensuring thread-local data separation for concurrent workers.

we optimized the cell.fillFromFields deserializer's extraction logic is inlined to eliminate closure and slice-of-structs overhead. Finally, we eliminated unnecessary array-clearing processes in cell.reset() (using existing length-gated reads instead) and changed computeCellHash to use stack-allocated buffers for prepending state hashes.

goos: linux
goarch: amd64
pkg: github.com/erigontech/erigon/execution/commitment
cpu: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
                            │ baseline_opt_2.txt │         optimize_opt_2.txt          │
                            │       sec/op       │   sec/op     vs base                │
CellReset                            61.78n ± 6%   57.01n ± 4%   -7.71% (p=0.000 n=10)
FillFromFields/AllFields             58.48n ± 6%   44.02n ± 4%  -24.72% (p=0.000 n=10)
FillFromFields/AccountOnly           26.98n ± 2%   11.72n ± 3%  -56.57% (p=0.000 n=10)
FillFromFields/HashOnly              34.68n ± 1%   18.15n ± 2%  -47.66% (p=0.000 n=10)
DeferredUpdatePrefix/Sparse          38.32n ± 3%   39.55n ± 4%   +3.20% (p=0.035 n=10)
DeferredUpdatePrefix/Dense           89.66n ± 2%   90.61n ± 2%        ~ (p=0.172 n=10)
geomean                              47.59n        35.23n       -25.97%

                            │ baseline_opt_2.txt │         optimize_opt_2.txt          │
                            │        B/op        │    B/op     vs base                 │
CellReset                           0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
FillFromFields/AllFields            0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
FillFromFields/AccountOnly          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
FillFromFields/HashOnly             0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
DeferredUpdatePrefix/Sparse         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
DeferredUpdatePrefix/Dense          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                        ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                            │ baseline_opt_2.txt │         optimize_opt_2.txt          │
                            │     allocs/op      │ allocs/op   vs base                 │
CellReset                           0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
FillFromFields/AllFields            0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
FillFromFields/AccountOnly          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
FillFromFields/HashOnly             0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
DeferredUpdatePrefix/Sparse         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
DeferredUpdatePrefix/Dense          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                        ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

@Sahil-4555 Sahil-4555 force-pushed the opt/mpt-commitment-zero-alloc branch from ac48acc to 73135f0 Compare April 24, 2026 13:26
Comment thread execution/commitment/commitment.go
Comment thread execution/commitment/hex_patricia_hashed.go Outdated
// getDeferredUpdate handles copying the prefix into its own prefixBuf.
upd := getDeferredUpdate(prefix, bitmap, touchMap, afterMap, cells, prev)

be.pendingPrefixes.Set(upd.prefix, struct{}{})
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't understand this move

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pendingPrefixes map holds a reference to the byte slice that serves as its key. The incoming prefix input specifies a shared, mutable scratch buffer (compactKeyBuf). If we send it directly to be.pendingPrefixes.Set(prefix) causes the map to keep a memory reference that will be overwritten on the next iteration of the folding loop, damaging the map's internal keys.

By invoking getDeferredUpdate first, we transfer the prefix bytes into the job's stable, isolated memory (update.prefixBuf). We then give upd.prefix to .Set(). Moving the call down here allows us to utilize the securely isolated byte slice as the map key, completely avoiding the aliasing problem and not needing to allocate a new copy

@AskAlexSharov
Copy link
Copy Markdown
Collaborator

also check last couple PR's here: https://github.com/erigontech/erigon/pulls/awskii

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets allocation/memory reductions in the execution/commitment Hex Patricia trie hot paths, primarily around key compaction/encoding and cell (de)serialization during fold/unfold and hashing.

Changes:

  • Added a caller-buffer-based HexToCompactBuf and switched fold/unfold branch-key encoding to reuse per-trie scratch buffers.
  • Reduced per-cell/per-branch overhead by inlining cell.fillFromFields parsing and removing array clearing in cell.reset.
  • Reduced deferred-update allocations by embedding a fixed prefix buffer inside pooled DeferredBranchUpdate objects.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
execution/commitment/nibbles/nibbles.go Adds HexToCompactBuf as a zero-allocation alternative to HexToCompact.
execution/commitment/hex_patricia_hashed.go Reuses scratch buffers for compact keys, inlines deserialization parsing, and avoids some allocation patterns in hashing paths.
execution/commitment/commitment.go Changes deferred branch update prefix handling to avoid per-update allocations by copying into an embedded buffer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread execution/commitment/hex_patricia_hashed.go Outdated
Comment thread execution/commitment/hex_patricia_hashed.go Outdated
Comment thread execution/commitment/nibbles/nibbles.go Outdated
Comment thread execution/commitment/nibbles/nibbles.go
@Sahil-4555 Sahil-4555 force-pushed the opt/mpt-commitment-zero-alloc branch from 73135f0 to b600d7a Compare April 24, 2026 16:51
@Sahil-4555 Sahil-4555 force-pushed the opt/mpt-commitment-zero-alloc branch from b600d7a to ca2cde0 Compare April 24, 2026 17:14
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