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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse single shared allocation for ABI data #970

Merged
merged 14 commits into from
Apr 22, 2024
Merged

Conversation

MartinquaXD
Copy link
Contributor

@MartinquaXD MartinquaXD commented Apr 19, 2024

Currently whenever you create a new instance of a contract the entire Abi gets cloned (which can be really huge) plus a bunch of signatures get computed (a lot of hashing) and cached. This is really costly considering that the Abi of a contract never changes so it would be an easy win to share the same allocation of those immutable pieces of data (Abi and computed hashes) across all instances of the same contract. This would make the creation of subsequent instances of the same contract mostly free.

This can already be achieved with ugly workarounds discussed and implemented here but if this issue gets resolved in the library itself manual allocation optimization will not be needed and the performance improvement for any code that creates a lot of contract instances would be huge.

The actual change is overall pretty small. I created a new type Interface that contains all the immutable data derived from the Abi. Then I gave it custom (De)Serialize implementations which mostly just do the same thing it did before (defer to Abi's implementation). The Deserialize implementation of course also computes the important hash and stores it's values together with the Abi.
Because the typesafe rust binding code generated by ethcontract-rs already stores the raw contract in a static allocation (which now contains the relevant immutable state Arced) we now get cheap contract instantiations.

Thanks @nlordell for the idea of fixing the performance issue directly in the library. 馃憤

The code also has a few unrelated changes to make the nightly CI build pass.

Breaking changes

  • Artifact::insert can now run into annoying borrow checker issues (that can be worked around, though)
  • Contract:abi is now accessible with Contract::interface::abi
  • Contract::interface::abi is harder to mutate than Contract::abi due to the introduction of the Arc

Overall I don't know how many people actually rely on this crate and whether or not these breaking changes would be a big deal for them. But even if this should not get merged into the main release of the crate I think this patch is still useful for anybody who needs to optimize performance across the board.

Test Plan

existing tests
ran cow protocol backend using the patched library and didn't see any obvious issues

@@ -204,26 +217,32 @@ mod test {

assert_eq!(artifact.len(), 0);

let insert_res = artifact.insert(make_contract("C1"));
{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately those scopes are needed due to us needed to update a modified contract. (see impl Drop for ContractMut<'_>.

ethcontract-common/src/artifact/hardhat.rs Show resolved Hide resolved
@MartinquaXD MartinquaXD changed the title Very hacky attempt at making ABI allocations static Reuse single shared allocation for ABI data Apr 20, 2024
@MartinquaXD MartinquaXD marked this pull request as ready for review April 20, 2024 08:29
// with the new ABI once the user is done updating the mutable contract.
let abi = self.0.interface.abi.clone();
let interface = Interface::from(abi);
*Arc::make_mut(&mut self.0.interface) = interface;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this panic if there is more than one Arc pointer to the data? Asking cuz my Rust is rusty :P

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this function is actually pretty cool. If there are other Arcs this will clone the current content of the Arc and make it point to the new allocation that can now safely be modified. If it's the only Arc it will mutate the content in place. It's slightly wonky because you could clone the original Arc, update the artifact and expect that your cloned Arc would see the updates but this is not the case. I think this is fine, though since fundamentally this behavior was already present before factoring in borrowing rules.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, cool - so it has CoW (copy on write) semantics. Very cool, and the name is a great coincidence 馃槄.

I think this is fine, though since fundamentally this behavior was already present before factoring in borrowing rules.

I would even argue this is desired to preserve the existing mutation semantics.

Copy link
Contributor

@fleupold fleupold left a comment

Choose a reason for hiding this comment

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

PR description is a bit thin on how the performance improvement is achieved but I think I get it (quite cool)

@MartinquaXD MartinquaXD merged commit b75f193 into main Apr 22, 2024
6 checks passed
@MartinquaXD MartinquaXD deleted the static-abi-allocation branch April 22, 2024 09:58
MartinquaXD added a commit to cowprotocol/services that referenced this pull request Apr 22, 2024
# Description
Bump `ethcontract-rs` version to make use of this performance
improvement: cowprotocol/ethcontract-rs#970

## How to test
CI
existing tests
MartinquaXD added a commit to cowprotocol/services that referenced this pull request Apr 23, 2024
# Description
Bump `ethcontract-rs` version to make use of this performance
improvement: cowprotocol/ethcontract-rs#970

## How to test
CI
existing tests
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.

None yet

3 participants