-
Notifications
You must be signed in to change notification settings - Fork 45
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
Reuse single shared allocation for ABI data #970
Conversation
@@ -204,26 +217,32 @@ mod test { | |||
|
|||
assert_eq!(artifact.len(), 0); | |||
|
|||
let insert_res = artifact.insert(make_contract("C1")); | |||
{ |
There was a problem hiding this comment.
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<'_>
.
// 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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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)
# Description Bump `ethcontract-rs` version to make use of this performance improvement: cowprotocol/ethcontract-rs#970 ## How to test CI existing tests
# Description Bump `ethcontract-rs` version to make use of this performance improvement: cowprotocol/ethcontract-rs#970 ## How to test CI existing tests
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 theAbi
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 theAbi
. Then I gave it custom(De)Serialize
implementations which mostly just do the same thing it did before (defer toAbi
's implementation). TheDeserialize
implementation of course also computes the important hash and stores it's values together with theAbi
.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 stateArc
ed) 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 withContract::interface::abi
Contract::interface::abi
is harder to mutate thanContract::abi
due to the introduction of theArc
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