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

Remove 14MB from RunPeerCommit allocations #5294

Merged
merged 1 commit into from
Mar 3, 2023

Conversation

benaadams
Copy link
Member

Rebased #5271 as was having issues with github 🤔

Changes

  • Only full refresh and parse from DB when nodes change
  • Don't allocate fresh node prior to checking if node already exists (compare prior rather then post)
  • Reduces by about ~10MB/min (see trace below)

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

image

@benaadams
Copy link
Member Author

Not sure why this change would crash the Test host process; isn't doing anything wild 🤔

@benaadams benaadams marked this pull request as draft February 17, 2023 16:23
@benaadams benaadams marked this pull request as ready for review February 19, 2023 07:08
@benaadams benaadams changed the title Reduce RunPeerCommit allocations Reduce RunPeerCommit allocations by 14MB Feb 19, 2023
@benaadams benaadams changed the title Reduce RunPeerCommit allocations by 14MB Reduce 14MB from RunPeerCommit allocations Feb 19, 2023
@benaadams benaadams changed the title Reduce 14MB from RunPeerCommit allocations Remove 14MB from RunPeerCommit allocations Feb 19, 2023
@benaadams benaadams mentioned this pull request Feb 20, 2023
12 tasks
public int PersistedNodesCount
public int PersistedNodesCount => GetPersistedNodes().Length;

public NetworkNode[] GetPersistedNodes()
Copy link
Member

Choose a reason for hiding this comment

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

Does it have to return an Array? Can it just return IReadOnlyCollection and we could leverage the list itself? We could avoid storing array and list and allocating array all the time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Recreating the array gets around the concurrency problem of the list being used while the list is being adjusted since its used in a few places and the nodes are created via events from the network; so it would need tighter synchronisation on the iteration in DiscoveryApp.AddPersistedNodes; NodesLoader.LoadPeersFromDb and PeerPool.UpdateReputationAndMaxPeersCount() with the node creation form ProtocolsManager.InitSyncPeerProtocol; DiscoveryApp.RunDiscoveryCommit(); DiscoveryManager.GetNodeLifecycleManager and removal from DiscoveryManager.OnIncomingMsg and PeerPool.CleanupPersistedPeers

It doesn't have the issue currently as it completely recreates the NetworkNode[] from scratch from the DB each time the method is called; whereas here its reused form most method calls (and it only reads from the DB once for app lifetime, though it continues to write changes to it, but updates in memory structure instead of reading back)

tl;dr yes it could, but is complicated due to the concurrency

Copy link
Member

@LukaszRozmej LukaszRozmej Feb 22, 2023

Choose a reason for hiding this comment

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

Fair enough, but you could have 2 lists that you would only copy from one to the other without any allocations or GC.

Or maybe we can do without copying and just exchange them?

Copy link
Member Author

Choose a reason for hiding this comment

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

🤔 could work; though use of the other list isn't guaranteed to have finished before starting to repopulate it

However thinking about that, could maybe get away with one List by taking a bounded Span reference via CollectionsMarshal.AsSpan<T>(List<T>); then adding some null checks when the items are returned and skipping them. Would need to look into it a bit more as might be a bit wild 😅

Copy link
Member

Choose a reason for hiding this comment

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

We have this two list approach in TxBroadcaster with _accumulatedTemporaryTxs and _txsToSend

Copy link
Member Author

@benaadams benaadams Feb 22, 2023

Choose a reason for hiding this comment

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

Looks like its a single threaded reader and when the method is re-entered you know the iteration is complete? (e.g. timer fire) so safe; whereas here its read and written from a bunch of sources?

Copy link
Member Author

Choose a reason for hiding this comment

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

Tried the 2 approach + Span, but is tricky to workout when to switch without it immediately switching back (e.g. UpdateNodes(IEnumerable<NetworkNode> nodes) calls UpdateNode(NetworkNode node) multiple times); and the Span still suffers from nodes being removed in the middle of the list, so skipping nodes 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably better to look at in future PR?

Comment on lines +68 to +70
NetworkNode node = GetNode(nodeRlp);
_nodesList.Add(node);
_nodePublicKeys.Add(node.NodeId);
Copy link
Member

Choose a reason for hiding this comment

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

We could potentially reuse already existing nodes instead of creating new ones all the time

Copy link
Member Author

Choose a reason for hiding this comment

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

RemoveNode and UpdateNode don't set the _nodes array to null but just replace it so GenerateNodes() should only be creating the nodes from scratch once during app lifetime (i.e. this bit of code) and UpdateNode gets passed in the preconstructed NetworkNode so it would require a fair bit of refactoring to reuse the nodes from RemoveNode 🤔

Copy link
Member

@LukaszRozmej LukaszRozmej Feb 22, 2023

Choose a reason for hiding this comment

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

So rlp starts with public key, which could be decoded on stack, then it could be reused from existing node instead of creating new one.

We can say it is out of scope of this PR and try to do it in some next one if ever - but sounds doable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I think would be good to look at that; as well as wider, as are a lot of temporary byte[] created for Rlp only to be fed into KeccakHash.Compute then thrown away; but the temp byte[] could be bypassed by feeding direct into KeccakHash which would also reduce copies by one

@benaadams
Copy link
Member Author

Rebased

@benaadams
Copy link
Member Author

Not sure why this test failed

  Failed Should_count_total_coinbase_payments [435 ms]
  Error Message:
   Expected object to be equal to 100000000000, but found 200000000000.

MevRpc doesn't involve the peer nodes? 🤔

@benaadams
Copy link
Member Author

Rebased again and squashed commits to see if persistent

@LukaszRozmej LukaszRozmej merged commit de71cc9 into NethermindEth:master Mar 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants