Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

126 speedup ammr #132

Merged
merged 9 commits into from
Apr 7, 2024
Merged

126 speedup ammr #132

merged 9 commits into from
Apr 7, 2024

Conversation

Sword-Smith
Copy link
Member

@Sword-Smith Sword-Smith commented Apr 7, 2024

Some optimizations of the archival-MMR which is used extensively by the archival mutator set.

The method prove_membership_async changes its interface to only return the authentication path (membership proof) and not also the peaks of the MMR. If the peaks are needed, they can be requested separately.

append is more than 2x faster, and more than 10x (30x in one measurement) faster for the append_and_persist benchmark. The other benchmarks show improvements of around 20-50 %.

append and batch_mutate_leaf_and_update_mps are the most relevant methods, as they are used by the archival mutator set which is what we're aiming to speed up in #127.

Previous code:

Timer precision: 20 ns
archival_mmr                         fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ append                                          │               │               │               │         │
│  ╰─ append_5000                                  │               │               │               │         │
│     ├─ append                      20.34 ms      │ 76.49 ms      │ 24.05 ms      │ 24.84 ms      │ 100     │ 100
│     ╰─ append_and_persist          35.23 ms      │ 571.3 ms      │ 316.2 ms      │ 315.8 ms      │ 100     │ 100
├─ batch_mutate_leaf_and_update_mps                │               │               │               │         │
│  ╰─ mutate_100_of_10000                          │               │               │               │         │
│     ├─ leaf_mutation               1.67 ms       │ 3.219 ms      │ 1.682 ms      │ 1.725 ms      │ 100     │ 100
│     ╰─ leaf_mutation_and_persist   23.85 ms      │ 37.78 ms      │ 28.61 ms      │ 28.21 ms      │ 100     │ 100
╰─ mutate                                          │               │               │               │         │
   ╰─ mutate_100_of_10000                          │               │               │               │         │
      ├─ leaf_mutation               1.907 ms      │ 2.419 ms      │ 1.943 ms      │ 2.002 ms      │ 100     │ 100
      ╰─ leaf_mutation_and_persist   30.19 ms      │ 45.85 ms      │ 35.24 ms      │ 36.32 ms      │ 100     │ 100

This PR:

Timer precision: 50 ns
archival_mmr                         fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ append                                          │               │               │               │         │
│  ╰─ append_5000                                  │               │               │               │         │
│     ├─ append                      8.73 ms       │ 62.24 ms      │ 9.767 ms      │ 10.69 ms      │ 100     │ 100
│     ╰─ append_and_persist          17.61 ms      │ 28.69 ms      │ 18.86 ms      │ 19.34 ms      │ 100     │ 100
├─ batch_mutate_leaf_and_update_mps                │               │               │               │         │
│  ╰─ mutate_100_of_10000                          │               │               │               │         │
│     ├─ leaf_mutation               1.479 ms      │ 2.247 ms      │ 1.485 ms      │ 1.537 ms      │ 100     │ 100
│     ╰─ leaf_mutation_and_persist   18.14 ms      │ 29.83 ms      │ 26.65 ms      │ 26.43 ms      │ 100     │ 100
╰─ mutate                                          │               │               │               │         │
   ╰─ mutate_100_of_10000                          │               │               │               │         │
      ├─ leaf_mutation               1.328 ms      │ 2.682 ms      │ 1.336 ms      │ 1.392 ms      │ 100     │ 100
      ╰─ leaf_mutation_and_persist   8.355 ms      │ 25.51 ms      │ 9.262 ms      │ 9.466 ms      │ 100     │ 100

This closes #126.

When appending a leaf to an MMR, we have the new leaf's authentication
path as we are building the MMR. So there's no reason to throw the
auth path away and then get the auth path later in a separate call.

This commit builds the auth path exactly once, and not twice as it was done
before. It also avoids unnecessary recursion.

This commit provides a more than 2x speedup for the mean and median case,
a 1.3x speedup for the slowest case and a more than 30x(!) speedup for cases
where the appends are followed by a `persist` to the DB.
Remove auth path arg from input to speed up the function for mutating a leaf
in an AMMR.

Speeds up the associated benchmark by ~20 %

Old mutate code:
╰─ mutate                                         │               │               │               │         │
   ╰─ mutate_100_of_10000                         │               │               │               │         │
      ├─ leaf_mutation              1.942 ms      │ 2.435 ms      │ 1.952 ms      │ 1.991 ms      │ 100     │ 100
        ╰─ leaf_mutation_and_persist  34.1 ms       │ 55.25 ms      │ 39.7 ms       │ 40.91 ms      │ 100     │ 100

new mutate code:
╰─ mutate                                         │               │               │               │         │
   ╰─ mutate_100_of_10000                         │               │               │               │         │
      ├─ leaf_mutation              1.607 ms      │ 1.922 ms      │ 1.613 ms      │ 1.656 ms      │ 100     │ 100
      ╰─ leaf_mutation_and_persist  20.36 ms      │ 27.2 ms       │ 22.44 ms      │ 22.73 ms      │ 100     │ 100
Since the function interface was changed, we no longer need to construct the
AMMR membership proof for each loop-iteration in the benchmark.
…or archival-MMR

This interface is changed as the MMR-accumulator and the achival-MMR no longer share
an interface, meaning that they can have separate function signatures. As the
archival-MMR does not need the membership-proofs of the leaves that are mutated, this
abstract arugment is removed.
No real noticable in performance, but this interface was not well liked,
so we remove the peaks from the return value. In theory this should be
slightly faster, as a step of the calculation is skipped, and since the
peak digests no longer need to be read from the database.
With this commit the DB is only accessed once when counting leaves, whereas
earlier, a helper function that read multiple digests from the MMRA was used.

Also changes index arithmetic for getting the relevant node indices for an
MMR authentication path slightly, and then reads all the digests in one
go instead of in a loop.

Good speedup seen from this commit:
- batch-mutation benchmark is 10 % faster, regular mutation is 20 % faster.
Copy link
Collaborator

@dan-da dan-da left a comment

Choose a reason for hiding this comment

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

Looks like a very good change, and I'm happy to see the benchmarks. nice!

I just had the one q about the new ArchivalMmr::append() impl.

self.prove_membership_async(leaf_index).await.0
let mut node_index = self.digests.len().await;
let leaf_index = node_index_to_leaf_index(node_index).unwrap();
let right_lineage_length = right_lineage_length_from_leaf_index(leaf_index);
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems we could just call prove_membership_async() as the old code did, and it would now be faster because it no longer returns peaks.

I assume this code is not doing that because prove_membership_async() would still be doing work that we don't need done here, is that correct?

Copy link
Member Author

@Sword-Smith Sword-Smith Apr 7, 2024

Choose a reason for hiding this comment

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

Yes. Here, we use each instance of left_sibling_hash for two purposes: For finding the parent digest in the Merkle tree, and as an element in the authentication path that append returns. Calling prove_membership_async would in essence mean we fetched the exact same digests twice.

@Sword-Smith Sword-Smith merged commit 3adb021 into master Apr 7, 2024
3 checks passed
@Sword-Smith Sword-Smith deleted the 126-speedup-ammr branch April 9, 2024 17:59
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.

Optimize IO in archival MMRs
2 participants