Skip to content

perf: skip Rayon on small Merkle tree levels#550

Closed
nicole-graus wants to merge 1 commit into
mainfrom
merkle-small-level-threshold
Closed

perf: skip Rayon on small Merkle tree levels#550
nicole-graus wants to merge 1 commit into
mainfrom
merkle-small-level-threshold

Conversation

@nicole-graus
Copy link
Copy Markdown
Collaborator

Adds a 1024-node threshold in hash_leaves and the per-level loop in build: levels below the threshold fall back to sequential iteration, avoiding Rayon task-scheduling overhead on small upper-level FRI trees.

Optimization extracted from PR #518.

@nicole-graus
Copy link
Copy Markdown
Collaborator Author

/bench 10


iter.map(|leaf| Self::hash_data(leaf)).collect()
{
if unhashed_leaves.len() >= 1024 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Low — Magic constant duplicated across files

1024 is also hardcoded in utils.rs:86. If the threshold is ever tuned, it must be changed in both places. Define a shared constant (e.g. in utils.rs or a new consts.rs) and reference it here:

Suggested change
if unhashed_leaves.len() >= 1024 {
if unhashed_leaves.len() >= PARALLEL_THRESHOLD {

.into_par_iter()
.zip(children_iter.par_chunks_exact(2));
{
if new_level_length >= 1024 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Low — Same magic constant as in traits.rs

Same threshold value should come from one named constant so the two sites stay in sync:

Suggested change
if new_level_length >= 1024 {
if new_level_length >= PARALLEL_THRESHOLD {

Comment on lines +93 to +110
} else {
new_level_iter
.iter_mut()
.zip(children_iter.chunks_exact(2))
.for_each(|(new_parent, children)| {
*new_parent = B::hash_new_parent(&children[0], &children[1]);
});
}
}
#[cfg(not(feature = "parallel"))]
let parent_and_children_zipped_iter =
new_level_iter.iter_mut().zip(children_iter.chunks_exact(2));

parent_and_children_zipped_iter.for_each(|(new_parent, children)| {
*new_parent = B::hash_new_parent(&children[0], &children[1]);
});
{
new_level_iter
.iter_mut()
.zip(children_iter.chunks_exact(2))
.for_each(|(new_parent, children)| {
*new_parent = B::hash_new_parent(&children[0], &children[1]);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Low — Sequential logic duplicated

The else branch (lines 93–100) and the #[cfg(not(feature = "parallel"))] block (lines 102–110) are byte-for-byte identical. If the sequential logic ever changes it must be updated twice. Extract into a small inline helper or macro, e.g.:

fn build_level_sequential<B: IsMerkleTreeBackend>(
    new_level_iter: &mut [B::Node],
    children_iter: &[B::Node],
) where B::Node: Clone {
    new_level_iter
        .iter_mut()
        .zip(children_iter.chunks_exact(2))
        .for_each(|(p, c)| *p = B::hash_new_parent(&c[0], &c[1]));
}

Then both sites call build_level_sequential::<B>(new_level_iter, children_iter);.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 23, 2026

Review: perf: skip Rayon on small Merkle tree levels

No security issues, no correctness bugs. The logic is sound — the early-return pattern in hash_leaves and the inline branch in build both correctly route small inputs to sequential iteration and large inputs to Rayon.

Three low-severity issues flagged inline:

# File Issue
1 traits.rs:20, utils.rs:86 1024 is a magic constant duplicated across two files — a single named constant (e.g. PARALLEL_THRESHOLD) would keep them in sync
2 utils.rs:93–110 The sequential iteration block is copy-pasted into both the else branch and the #[cfg(not(feature = "parallel"))] block; extract a small helper to avoid the two-site maintenance burden
3 Tests All existing tests use ≤ 16 elements, so the parallel branch (>= 1024) has no test coverage — a single test with 1024+ elements would confirm the parallel and sequential paths produce identical results

Nothing blocking. Issue 3 is the most worth addressing before merge.

@github-actions
Copy link
Copy Markdown

Codex Code Review

No issues found in the reviewed diff.

The changes in traits.rs and utils.rs look like a straightforward performance guard around Rayon usage and do not appear to change Merkle-tree correctness, memory safety, or security properties.

Validation gap: I could not run cargo test/cargo check in this environment because the workspace attempts to resolve an external git dependency from executor, and network access is blocked.

@github-actions
Copy link
Copy Markdown

Benchmark — fib_iterative_8M (median of 10)

Table parallelism: 32 (auto = cores / 3)

Metric main PR Δ
Peak heap 64421 MB 63689 MB -732 MB (-1.1%) ⚪
Prove time 31.876s 31.877s +0.001s (+0.0%) ⚪

✅ No significant change.

✅ Low variance (time: 2.2%, heap: 3.0%)

Commit: c0fc4b4 · Baseline: built from main · Runner: self-hosted bench

@diegokingston diegokingston deleted the merkle-small-level-threshold branch May 20, 2026 12:49
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