Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Less cloning when importing blocks #11531

Merged
merged 19 commits into from
Mar 4, 2020

Conversation

dvdplm
Copy link
Collaborator

@dvdplm dvdplm commented Feb 28, 2020

Try to reduce the number of clones in general and of the block raw bytes in particular. Block RLP is typically 15 - 40kb in size so I think it makes sense to keep the number of clones we make to a minimum. Another aspect of unnecessary cloning is that it obfuscates the intention of the code making it harder to read and follow; to address this this PR tries to move the clones that are necessary to the end of the processing flow.

During the work I noiticed that the AuxiliaryData and AuxiliaryRequest types were not necessary. I extracted their removal to #11533. It is probably best to review that PR first.

UPDATE I used the alloc_counter crate to instrument importing the exact same 60 blocks with and without the changes in this PR. The good news that yes, with this PR we do allocate less often. The bad news is that the change is minscule: 2 177 227 (master) vs 2 176 794 (branch); something somewhere is allocating a lot.

Master:

2020-03-04 14:51:14  main INFO ethcore_service::service  Configured for Ethereum using Ethash engine
2020-03-04 14:51:16  Verifier #7 TRACE dp  Imported 1 blocks.
	Allocation stats. Allocations=40910/40910.00 per block, reallocations=1230/1230.00 per block, deallocations=38959/38959.00 per block
2020-03-04 14:51:20  IO Worker #0 INFO import  Syncing #9598780 0x2b5a…7408     1.00 blk/s  111.8 tx/s    8.5 Mgas/s      0+   47 Qed     148 KiB chain 96 MiB db 5 MiB queue
2020-03-04 14:51:25  IO Worker #1 INFO import  Syncing #9598786 0x52aa…94dc     1.20 blk/s   95.6 tx/s   10.3 Mgas/s      0+   47 Qed     218 KiB chain 97 MiB db 5 MiB queue
2020-03-04 14:51:27  Verifier #5 TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=430769/35897.42 per block, reallocations=11694/974.50 per block, deallocations=424528/35377.33 per block
2020-03-04 14:51:30  IO Worker #3 INFO import  Syncing #9598790 0xf959…031d     0.80 blk/s  107.4 tx/s    7.2 Mgas/s      0+   35 Qed     331 KiB chain 98 MiB db 3 MiB queue
2020-03-04 14:51:35  IO Worker #3 INFO import  Syncing #9598795 0x8073…778a     1.00 blk/s  149.9 tx/s    8.6 Mgas/s      0+   35 Qed     471 KiB chain 100 MiB db 3 MiB queue
2020-03-04 14:51:40  main TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=435572/36297.67 per block, reallocations=11564/963.67 per block, deallocations=432038/36003.17 per block
2020-03-04 14:51:40  IO Worker #3 INFO import  Syncing #9598800 0xe8f1…4085     1.00 blk/s   98.3 tx/s    8.5 Mgas/s      0+   23 Qed     596 KiB chain 101 MiB db 2 MiB queue
2020-03-04 14:51:45  IO Worker #3 INFO import  Syncing #9598804 0x5467…cd82     0.80 blk/s  106.7 tx/s    7.4 Mgas/s      0+   23 Qed     596 KiB chain 102 MiB db 2 MiB queue
2020-03-04 14:51:50  IO Worker #3 INFO import  Syncing #9598809 0xc0e6…eef8     1.00 blk/s   89.4 tx/s    9.0 Mgas/s      0+   23 Qed     1004 KiB chain 103 MiB db 2 MiB queue
2020-03-04 14:51:52  IO Worker #2 TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=413329/34444.08 per block, reallocations=10276/856.33 per block, deallocations=410683/34223.58 per block
2020-03-04 14:51:55  IO Worker #2 INFO import  Syncing #9598814 0x47fa…992b     1.00 blk/s  126.4 tx/s    9.4 Mgas/s      0+   11 Qed     1004 KiB chain 105 MiB db 895 KiB queue
2020-03-04 14:52:00  IO Worker #2 INFO import  Syncing #9598819 0xc2b6…0b9e     1.00 blk/s  140.5 tx/s   10.0 Mgas/s      0+   11 Qed     1004 KiB chain 106 MiB db 895 KiB queue
2020-03-04 14:52:05  IO Worker #2 INFO import  Syncing #9598823 0xca8e…f89d     0.80 blk/s  116.8 tx/s    7.9 Mgas/s      0+   11 Qed     1004 KiB chain 107 MiB db 895 KiB queue
2020-03-04 14:52:05  IO Worker #0 TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=473347/39445.58 per block, reallocations=12050/1004.17 per block, deallocations=470434/39202.83 per block
2020-03-04 14:52:16  main TRACE dp  Imported 0 blocks.
	Allocation stats. Allocations=3/inf per block, reallocations=0/NaN per block, deallocations=3/inf per block
2020-03-04 14:52:16  IO Worker #3 TRACE dp  Imported 0 blocks.
	Allocation stats. Allocations=3/inf per block, reallocations=0/NaN per block, deallocations=3/inf per block
2020-03-04 14:52:16  main INFO parity_ethereum::blockchain  Import completed in 62 seconds, 60 blocks, 0 blk/s, 7052 transactions, 112 tx/s, 536 Mgas, 8 Mgas/s
2020-03-04 14:52:16  IO Worker #1 TRACE dp  Imported 11 blocks.
	Allocation stats. Allocations=383294/34844.91 per block, reallocations=9403/854.82 per block, deallocations=381205/34655.00 per block

This branch:

2020-03-04 14:48:51  main INFO ethcore_service::service  Configured for Ethereum using Ethash engine
2020-03-04 14:48:53  Verifier #5 TRACE dp  Imported 1 blocks.
	Allocation stats. Allocations=40902/40902.00 per block, reallocations=1229/1229.00 per block, deallocations=38951/38951.00 per block
2020-03-04 14:48:56  IO Worker #0 INFO import  Syncing #9598778 0xb1ea…cd61     0.60 blk/s   62.4 tx/s    4.5 Mgas/s      0+   47 Qed     113 KiB chain 82 MiB db 5 MiB queue
2020-03-04 14:49:01  IO Worker #2 INFO import  Syncing #9598783 0xdee0…a031     1.00 blk/s  103.6 tx/s    9.9 Mgas/s      0+   47 Qed     148 KiB chain 84 MiB db 5 MiB queue
2020-03-04 14:49:06  Verifier #7 TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=430684/35890.33 per block, reallocations=11694/974.50 per block, deallocations=424443/35370.25 per block
2020-03-04 14:49:06  IO Worker #0 INFO import  Syncing #9598788 0x0e10…ea89     1.00 blk/s   97.3 tx/s    8.3 Mgas/s      0+   35 Qed     331 KiB chain 85 MiB db 3 MiB queue
2020-03-04 14:49:11  IO Worker #0 INFO import  Syncing #9598793 0x6f13…968e     1.00 blk/s  152.8 tx/s    9.2 Mgas/s      0+   35 Qed     471 KiB chain 87 MiB db 3 MiB queue
2020-03-04 14:49:16  IO Worker #2 INFO import  Syncing #9598798 0x5861…34f7     1.00 blk/s  109.2 tx/s    8.6 Mgas/s      0+   35 Qed     471 KiB chain 88 MiB db 3 MiB queue
2020-03-04 14:49:18  main TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=435482/36290.17 per block, reallocations=11563/963.58 per block, deallocations=431948/35995.67 per block
2020-03-04 14:49:21  IO Worker #2 INFO import  Syncing #9598802 0x816f…f0a1     0.80 blk/s   88.4 tx/s    6.3 Mgas/s      0+   23 Qed     596 KiB chain 89 MiB db 2 MiB queue
2020-03-04 14:49:26  IO Worker #2 INFO import  Syncing #9598807 0xa794…f641     1.00 blk/s  107.4 tx/s    9.6 Mgas/s      0+   23 Qed     1004 KiB chain 90 MiB db 2 MiB queue
2020-03-04 14:49:31  IO Worker #3 TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=413244/34437.00 per block, reallocations=10276/856.33 per block, deallocations=410598/34216.50 per block
2020-03-04 14:49:31  IO Worker #3 INFO import  Syncing #9598813 0xc5c0…e286     1.20 blk/s  149.1 tx/s   10.7 Mgas/s      0+   11 Qed     1004 KiB chain 91 MiB db 895 KiB queue
2020-03-04 14:49:36  IO Worker #3 INFO import  Syncing #9598817 0x20b4…a291     0.80 blk/s  102.0 tx/s    7.7 Mgas/s      0+   11 Qed     1004 KiB chain 92 MiB db 895 KiB queue
2020-03-04 14:49:41  IO Worker #3 INFO import  Syncing #9598821 0x48cf…4a5e     0.80 blk/s   99.8 tx/s    7.9 Mgas/s      0+   11 Qed     1004 KiB chain 94 MiB db 895 KiB queue
2020-03-04 14:49:44  IO Worker #1 TRACE dp  Imported 12 blocks.
	Allocation stats. Allocations=473262/39438.50 per block, reallocations=12050/1004.17 per block, deallocations=470349/39195.75 per block
2020-03-04 14:49:56  main TRACE dp  Imported 0 blocks.
	Allocation stats. Allocations=2/inf per block, reallocations=0/NaN per block, deallocations=2/inf per block
2020-03-04 14:49:56  IO Worker #2 TRACE dp  Imported 0 blocks.
	Allocation stats. Allocations=2/inf per block, reallocations=0/NaN per block, deallocations=2/inf per block
2020-03-04 14:49:56  main INFO parity_ethereum::blockchain  Import completed in 65 seconds, 60 blocks, 0 blk/s, 7052 transactions, 108 tx/s, 536 Mgas, 8 Mgas/s
2020-03-04 14:49:56  IO Worker #0 TRACE dp  Imported 11 blocks.
	Allocation stats. Allocations=383216/34837.82 per block, reallocations=9403/854.82 per block, deallocations=381127/34647.91 per block

@@ -168,16 +168,17 @@ impl<'x> OpenBlock<'x> {
/// Push a transaction into the block.
///
/// If valid, it will be executed, and archived together with the receipt.
pub fn push_transaction(&mut self, t: SignedTransaction) -> Result<&Receipt, Error> {
pub fn push_transaction(&mut self, t: &SignedTransaction) -> Result<&Receipt, Error> {
Copy link
Collaborator

@niklasad1 niklasad1 Mar 2, 2020

Choose a reason for hiding this comment

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

The rationale behind this change?

The transaction is still cloned if the else branch is taken, are you doing this because of saving an allocation if the if branch is taken?

Not sure how this plays along w.r.t. to speculative execution on modern CPUs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wait, which else-branch do you mean?

Copy link
Collaborator

@niklasad1 niklasad1 Mar 2, 2020

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see, well there's that also, but mainly passing the tx by ref is fallout from a borrow from higher up in the call chain, in enact_verified(). (I was also unsure wth the into() was doing).

Copy link
Collaborator

Choose a reason for hiding this comment

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

The into() was not doing anything

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm also not sure why we need this change, if anything, it makes more allocations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So in import_verified_blocks() we call check_and_lock_block() passing in an owned PreverifiedBlock, so to make the rest of the code work there we do some cloning that didn't seem necessary and that's where this PR started.
When we call enact_verified() we move the block but what is actually needed for enact() is actually the header, transactions and the (max) 2 uncles.
What I'd really would like to do is have a way to split up the PreverifiedBlock into (owned) components and be able to move them independently. I don't know of a way to do that, and that's why I ended up with this: it avoids cloning the block bytes in import_verified_blocks() and a few Header structs, and passes references around instead. Maybe this mean more clones, as each transaction that is added to the block is cloned, but they are also smaller (I hope).

Copy link
Collaborator

@niklasad1 niklasad1 Mar 3, 2020

Choose a reason for hiding this comment

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

I guess you could use some mem::take tricks, for example, replace Bytes with an empty Vec.

But, why can't you take PreverifiedBlock by value instead in enact? As I did here: https://github.com/OpenEthereum/open-ethereum/compare/na-ethcore-blockerror#diff-e2e171033e005c3b329818aaff213767R411-R564

I'm not saying that it is elegant but it worked for me at least :)

Copy link
Collaborator

@ordian ordian Mar 3, 2020

Choose a reason for hiding this comment

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

This whole thing reminds me of something :P

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.”

Seriously though,

What I'd really would like to do is have a way to split up the PreverifiedBlock into (owned) components and be able to move them independently. I don't know of a way to do that

create a type for parts or pass them directly instead of PreferifiedBlock? or what Niklas suggested

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, thank you for the input, much appreciated. At the end I went with mem::take()-ing the block RLP bytes and after some further shuffling things around I think I have something decent.

ethcore/src/block.rs Outdated Show resolved Hide resolved
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
let mut invalid_blocks = HashSet::new();
let proposed_blocks = Vec::with_capacity(max_blocks_to_import);
Copy link
Collaborator

@niklasad1 niklasad1 Mar 2, 2020

Choose a reason for hiding this comment

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

this was actually not ever used? Nice catch 👍

ethcore/src/block.rs Outdated Show resolved Hide resolved
@dvdplm dvdplm changed the title WIP Do less cloning when importing blocks Less cloning when importing blocks Mar 3, 2020
@dvdplm dvdplm self-assigned this Mar 3, 2020
@dvdplm dvdplm added the A0-pleasereview 🤓 Pull request needs code review. label Mar 3, 2020
@dvdplm dvdplm marked this pull request as ready for review March 3, 2020 09:18
Copy link
Collaborator

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

I like this, neat.

However, the are some uncommented code that needs to be removed before merge and resolve the conflicts (that's probably my fault sorry)

ethcore/types/src/client_types.rs Outdated Show resolved Hide resolved
ethcore/src/client/client.rs Show resolved Hide resolved
ethcore/src/client/client.rs Outdated Show resolved Hide resolved
dvdplm and others added 2 commits March 3, 2020 16:24
* master:
  Remove AuxiliaryData/AuxiliaryRequest (#11533)
  [journaldb]: cleanup (#11534)
  Remove references to parity-ethereum (#11525)
  Drop IPFS support (#11532)
ethcore/src/block.rs Outdated Show resolved Hide resolved
ethcore/src/miner/miner.rs Outdated Show resolved Hide resolved
if is_invalid {
invalid_blocks.insert(hash);
continue;
}

let block_bytes = std::mem::take(&mut block.bytes);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Taking the RLP here, so I can move the block in the next line.

@@ -412,8 +410,10 @@ impl Importer {

let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some();

let enact_result = enact_verified(
block,
let enact_result = enact(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Calling enact() directly here lets me avoid splitting up the PreverifiedBlock before making the call (and pass the Header by ref).

Copy link
Collaborator

Choose a reason for hiding this comment

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

very neat

Copy link
Collaborator

@ordian ordian Mar 4, 2020

Choose a reason for hiding this comment

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

I guess enact_verified added a bit of type safety to enact. Another approach would be to keep that function, but change the return type to return unused but consumed fields of PreverifiedBlock, something like EthcoreResult<(LockedBlock, Option<_>, Bytes)> or (EthcoreResult<_>, Bytes).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I tried that, and Niklas did too I think. It gets pretty nasty and I'd rather not. It feels like a dirty trick to hand in data and ask for it back.

Copy link
Collaborator

@niklasad1 niklasad1 Mar 4, 2020

Choose a reason for hiding this comment

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

Yeah, it gets messy because then we need to return Bytes in both the Ok and Err case because of this damn BadBlocks::report

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, we can merge the PR as is, I can take a look if it could be cleaned up.

@dvdplm dvdplm requested review from niklasad1 and ordian March 4, 2020 12:46
if is_invalid {
invalid_blocks.insert(hash);
continue;
}

let block_bytes = std::mem::take(&mut block.bytes);
match self.check_and_lock_block(block, client) {
Copy link
Collaborator

@niklasad1 niklasad1 Mar 4, 2020

Choose a reason for hiding this comment

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

Hmm, this will call check_and_lock_block with empty block bytes.

Then we must ensure that it and all the functions it calls don't use block bytes, have you done it?

Well, I propose to create another type PreverifiedBlockWithoutBytes or something.
If that's not possible add a big fat note in fn check_and_lock_block which explains that block bytes is empty.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Then we must ensure that it and all the functions it calls don't use block bytes, have you done it?

I have yes. It is not used lower down in the call graph. I'll add a big comment about it.

@niklasad1 niklasad1 added A8-looksgood 🦄 Pull request is reviewed well. M4-core ⛓ Core client code / Rust. and removed A0-pleasereview 🤓 Pull request needs code review. labels Mar 4, 2020
@ordian ordian merged commit 6c0134f into master Mar 4, 2020
@ordian ordian deleted the dp/perf/less-cloning-when-importing-blocks branch March 4, 2020 17:34
ordian added a commit that referenced this pull request Mar 5, 2020
ordian added a commit that referenced this pull request Mar 5, 2020
* ethcore: cleanup after #11531

* fix verification benches

* ethcore: remove enact_verified

* ethcore: replace PreverifiedBlock with a tuple

* ethcore: more descriptive Vec<u8> type alias
ordian added a commit that referenced this pull request Mar 6, 2020
* master:
  ethcore: cleanup after #11531 (#11546)
  license update (#11543)
  Less cloning when importing blocks (#11531)
  Github Actions (#11528)
  Fix Alpine Dockerfile (#11538)
  Remove AuxiliaryData/AuxiliaryRequest (#11533)
  [journaldb]: cleanup (#11534)
ordian added a commit that referenced this pull request Mar 10, 2020
* master:
  Code cleanup in the sync module (#11552)
  initial cleanup (#11542)
  Warn if genesis constructor revert (#11550)
  ethcore: cleanup after #11531 (#11546)
  license update (#11543)
  Less cloning when importing blocks (#11531)
  Github Actions (#11528)
  Fix Alpine Dockerfile (#11538)
  Remove AuxiliaryData/AuxiliaryRequest (#11533)
  [journaldb]: cleanup (#11534)
  Remove references to parity-ethereum (#11525)
  Drop IPFS support (#11532)
  chain-supplier: fix warning reporting for GetNodeData request (#11530)
  Faster kill_garbage (#11514)
  [EngineSigner]: don't sign message with only zeroes (#11524)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A8-looksgood 🦄 Pull request is reviewed well. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants