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

Benchmark Ethereum Pallet #149

Merged
merged 30 commits into from
Jul 10, 2020
Merged

Benchmark Ethereum Pallet #149

merged 30 commits into from
Jul 10, 2020

Conversation

HCastano
Copy link
Contributor

@HCastano HCastano commented Jun 25, 2020

The goal of this PR is to add benchmarks to the Ethereum bridge pallet. There are only two extrinsics we care about here: import_unsigned_headers() and import_signed_headers(). For each of these extrinsics we need to make sure that we cover the best and worst case run times.

There are four main operations whose performance we should check each extrinsic:

  • Finalization
  • Pruning
  • Receipts (through authority set changes I think)
  • Chain Re-Orgs

I think we should benchmark each of these individually, and combine them later if needed to find the worst case performance.

If I've understood the benchmarking correctly we want to see how the run time is affected by the complexity parameter. For finalization I think we should use the number of blocks being finalized as the complexity parameter. For pruning it should be the number of blocks, but in practice it doesn't matter because we're limited to the number of blocks we can prune per import anyways (eight in our case). For receipts I'm not sure how the complexity parameter will relate to the bench. For re-orgs it would be the length of the chain being re-org'd.

@HCastano HCastano requested a review from tomusdrw June 25, 2020 03:02
@HCastano HCastano added this to the release critical milestone Jun 25, 2020
@svyatonik
Copy link
Contributor

For receipts I think the complexity could be sizeof(receipts) - nothing else comes to my mind. Nothing irregular happens during reorg => it shouldn't affect complexity at all (because we don't have concept of 'canonical unfinalized chain' and reorgs could only happen in unfinalized part). One other thing that may affect performance a bit (not as much as len(unfinalized-chain), though) could be number of validators in the set.

Dump of my thoughts:

  1. I haven't finished reading benchmarking docs yet, but I believe that at the end, we should have some 'worst case' test (with our guesses for worst parameters values) and 'average case' test (which will be used to determine default final weights) right?
  2. we may use benchmarking results (in future?) to adjust actual weight using this suggestion;
  3. for finality worst case - we should guess some meaningful value that we hope won't actually happen on the PoA chain? I think that maybe we could use this guess? It is used in similar context. Wdyt?

// blocks has on our import time. As such we need to make sure that we keep the number of
// validators fixed while changing the number blocks finalized (the complixity parameter) by
// importing the last header.
import_unsigned_finality {
Copy link
Contributor

Choose a reason for hiding this comment

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

One last thing I forgot to add to my top-level comment - imo weight(import_signed_headers) is actually weight(import_unsigned_header) * len(headers) + some_constant, where some_constant stands for recovering signature + iterating headers + invoking callbacks (which are noop in our runtime - not sure how to measure that in general case?). So we may only introduce benchmarks for unsigned version and use results for signed version.

@svyatonik
Copy link
Contributor

So I read benchmarking doc and it suggests to use worst case estimate in weights computation. Worst case means many unfinalized blocks for us && it may be too much for regular header import. So my suggestion is to implement point 2 from above list (i.e. use this suggestion). The final weight would be: regular-block-import-weight + isset(receipts.is_some()) * receipts-degrade-factor + number-of-unfinalized-headers * finalization-degrade-factor + number-of-pruned-headers * pruning-degrade-factor.

So we need benchmarks that Hernando has originally suggested to get these factors, then everything else should be quite easy to implement.

@HCastano
Copy link
Contributor Author

HCastano commented Jul 5, 2020

I think this is ready for feedback now. One thing I want to point out is that I'm not sure if my usage of the complexity parameter is correct. For example, in the pruning and receipt benches it's not used at all. I don't see a way to use it meaningfully since both of these are fixed regardless of the number of blocks imported.

I also did a first pass for the benchmarks using a release build of the branch and --steps 50 --repeat 20. The table below summarizes the results of the "Min Squares Analysis" provided by the benchmark.

Benchmark Time (µs)
import_unsigned_best_case 195.1 + 0.002
import_unsigned_finality 198.7 + 12.34
import_unsigned_finality_with_cache 251.9 + 0.243
import_unsigned_pruning 201.1 + 12.81
import_unsigned_with_receipts 204.3 + 2.439

I haven't included any CI changes in this PR to build/test the benchmarks. I think we should wait until some of the new CI changes are merged to master before doing that.

@HCastano HCastano marked this pull request as ready for review July 5, 2020 18:26
@HCastano HCastano requested a review from svyatonik July 5, 2020 18:27
modules/ethereum/src/benchmarking.rs Outdated Show resolved Hide resolved
modules/ethereum/src/benchmarking.rs Outdated Show resolved Hide resolved
As part of this change we have removed the hardcoded TEST_RECEIPT_ROOT
and instead chose to calculate the receipt root on the fly. This will
make tests and benches less fragile.
Copy link
Contributor

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

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

It looks really good! Couple of tiny grumbles and looking forward for the results (graphs would be nice :))

bin/node/runtime/src/lib.rs Show resolved Hide resolved
modules/ethereum/src/benchmarking.rs Show resolved Hide resolved
modules/ethereum/src/benchmarking.rs Show resolved Hide resolved
modules/ethereum/src/benchmarking.rs Show resolved Hide resolved
modules/ethereum/src/benchmarking.rs Show resolved Hide resolved
@HCastano
Copy link
Contributor Author

HCastano commented Jul 7, 2020

I've generated some plots. I first got the benchmarking datapoints as so:

./target/release/bridge-node benchmark --pallet bridge-eth-poa --extrinsic $extrinsic --steps 50 --repeat 20 --raw > $extrinsic.txt

Then then I pasted the contents of the file into here.

Import Unsigned Header

import_unsigned_header_best_case

Import Unsigned Finality

import_unsigned_finality

Import Unsigned Finality with Cache

import_unsigned_finality_with_cache

Import Unsigned Pruning

import_unsigned_pruning

Import Unsigned Receipts

import_unsigned_with_receipts

The finality cache plot is my personal favourite, lol.

@tomusdrw
Copy link
Contributor

tomusdrw commented Jul 8, 2020

The plots looks good! So it seems that either things are constant (or at least amortized) or linear, which is really good. Can we plot our formulas on top of this to make sure we have an upper bound? Then let's get this merged and deployed :)

@tomusdrw tomusdrw merged commit d0e3d1c into master Jul 10, 2020
@tomusdrw tomusdrw deleted the hc-bench-eth-pallet branch July 10, 2020 09:10
serban300 pushed a commit to serban300/parity-bridges-common that referenced this pull request Mar 27, 2024
* Add skeleton for worst case import_unsigned_header

* Fix a typo

* Add benchmark test for best case unsigned header import

* Add finality verification to worst case bench

* Move `insert_header()` from mock to test_utils

Allows the benchmarking code to use this without having to pull it in from the mock.

* Add a rough bench to test a finalizing a "long" chain

* Try to use complexity parameter for finality bench

* Improve long finality bench

* Remove stray dot file

* Remove old "worst" case bench

* Scribble some ideas down for pruning bench

* Prune headers during benchmarking

* Clean up some comments

* Make finality bench work for entire range of complexity parameter

* Place initialization code into a function

* Add bench for block finalization with caching

* First attempt at bench with receipts

* Try and trigger validator set change

* Perform a validator set change during benchmarking

* Move `validators_change_receipt()` to shared location

Allows unit tests and benchmarks to access the same helper function
and const

* Extract a test receipt root into a constant

* Clean up description of pruning bench

* Fix cache and pruning tests

* Remove unecessary `build_custom_header` usage

* Get rid of warnings

* Remove code duplication comment

I don't think its entirely worth it to split out so few lines of code.
The benches aren't particularly hard to read anyways.

* Increase the range of the complexity parameter

* Use dynamic number of receipts while benchmarking

As part of this change we have removed the hardcoded TEST_RECEIPT_ROOT
and instead chose to calculate the receipt root on the fly. This will
make tests and benches less fragile.

* Prune a dynamic number of headers
serban300 pushed a commit to serban300/parity-bridges-common that referenced this pull request Apr 8, 2024
* Add skeleton for worst case import_unsigned_header

* Fix a typo

* Add benchmark test for best case unsigned header import

* Add finality verification to worst case bench

* Move `insert_header()` from mock to test_utils

Allows the benchmarking code to use this without having to pull it in from the mock.

* Add a rough bench to test a finalizing a "long" chain

* Try to use complexity parameter for finality bench

* Improve long finality bench

* Remove stray dot file

* Remove old "worst" case bench

* Scribble some ideas down for pruning bench

* Prune headers during benchmarking

* Clean up some comments

* Make finality bench work for entire range of complexity parameter

* Place initialization code into a function

* Add bench for block finalization with caching

* First attempt at bench with receipts

* Try and trigger validator set change

* Perform a validator set change during benchmarking

* Move `validators_change_receipt()` to shared location

Allows unit tests and benchmarks to access the same helper function
and const

* Extract a test receipt root into a constant

* Clean up description of pruning bench

* Fix cache and pruning tests

* Remove unecessary `build_custom_header` usage

* Get rid of warnings

* Remove code duplication comment

I don't think its entirely worth it to split out so few lines of code.
The benches aren't particularly hard to read anyways.

* Increase the range of the complexity parameter

* Use dynamic number of receipts while benchmarking

As part of this change we have removed the hardcoded TEST_RECEIPT_ROOT
and instead chose to calculate the receipt root on the fly. This will
make tests and benches less fragile.

* Prune a dynamic number of headers
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