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

fix(cli): improve performance of linked library resolution during deployment #3197

Merged
merged 8 commits into from
Sep 19, 2024

Conversation

alvrs
Copy link
Member

@alvrs alvrs commented Sep 18, 2024

Significantly improved the deployment performance for large projects with public libraries by implementing a more efficient algorithm to resolve public libraries during deployment.
The local deployment time on a large reference project was reduced from over 10 minutes to 4 seconds.

Copy link

changeset-bot bot commented Sep 18, 2024

🦋 Changeset detected

Latest commit: 0bce659

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
@latticexyz/cli Patch
@latticexyz/world-modules Patch
mock-game-contracts Patch
@latticexyz/abi-ts Patch
@latticexyz/block-logs-stream Patch
@latticexyz/common Patch
@latticexyz/config Patch
create-mud Patch
@latticexyz/dev-tools Patch
@latticexyz/explorer Patch
@latticexyz/faucet Patch
@latticexyz/gas-report Patch
@latticexyz/protocol-parser Patch
@latticexyz/react Patch
@latticexyz/recs Patch
@latticexyz/schema-type Patch
solhint-config-mud Patch
solhint-plugin-mud Patch
@latticexyz/stash Patch
@latticexyz/store-indexer Patch
@latticexyz/store-sync Patch
@latticexyz/store Patch
@latticexyz/utils Patch
@latticexyz/world-module-metadata Patch
@latticexyz/world Patch
ts-benchmarks Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines 12 to 14
const library = libraries.find((lib) => lib.path === placeholder.path && lib.name === placeholder.name);
const libraryKey = getLibraryKey(placeholder);
const library = libraryMap[libraryKey];
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously we were doing a linear search to find the matching library every time is function was called, now we're just looking up the matching library in constant time. Doesn't make a big difference with small numbers of libraries but compounds with many libraries.

Comment on lines 16 to 22
bytecode = spliceHex(
bytecode,
placeholder.start,
placeholder.length,
library.prepareDeploy(deployer, libraries).address,
);

// Store the prepared address in the library map to avoid preparing the same library twice
library.address ??= library.prepareDeploy(deployer, libraryMap).address;

bytecode = spliceHex(bytecode, placeholder.start, placeholder.length, library.address);
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously we were resolving each library multiple times if it was used in multiple contracts/libraries. The time complexity of this quickly explodes if there are many libraries that use other libraries. Now we're caching a library's resolved address, so each library is only resolved once.

@@ -133,6 +133,7 @@ export async function runDeploy(opts: DeployOptions): Promise<WorldDeploy> {
: undefined,
}),
account,
pollingInterval: 100,
Copy link
Member Author

Choose a reason for hiding this comment

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

Reducing polling time for a short time during deployment should be safe and makes a significant impact on deployment time (with the project i was testing with this change brought the time from 16s to 4s after the previous improvements were implemented)

Copy link
Member Author

Choose a reason for hiding this comment

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

(moved this to waitForTransactions instead to only increase the polling rate where it matters)

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved this change to #3198 since it's unrelated to library linking, and CI wasn't happy with it yet

}

export function getLibraryKey({ path, name }: { path: string; name: string }): string {
return `${path}:${name}`;
Copy link
Member

@holic holic Sep 19, 2024

Choose a reason for hiding this comment

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

should we include the deployer address in the key since it changes the bytecode and thus the address?

Copy link
Member Author

Choose a reason for hiding this comment

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

the deployer address is passed in later (in prepareDeploy) while the map is created before, but we can change address in the map to { [deployer: Address]: Address } to cache per deployer address

throw new Error("world deploy failed");
}

const [receipt] = await waitForTransactions({ client, hashes: [tx], debugLabel: "world deploy" });
Copy link
Member

@holic holic Sep 19, 2024

Choose a reason for hiding this comment

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

iirc I intentionally didn't replace this one with waitForTransactions because I wanted to throw a more specific error, but this is fine too

Copy link
Member Author

Choose a reason for hiding this comment

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

reason i changed this was bc i changed polling settings in waitForTransactions and would have had to duplicate the changes here, but moved that to #3198 so could also move this change there

@@ -105,7 +105,7 @@ export async function verify({
);

modules.map(({ name, prepareDeploy }) => {
const { address } = prepareDeploy(deployerAddress, []);
const { address } = prepareDeploy(deployerAddress, {});
Copy link
Member

Choose a reason for hiding this comment

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

thoughts on defaulting the second arg in prepareDeploy so callers don't need to know the shape of the thing to pass in

export type LibraryMap = { [key: string]: Library & { address?: Hex } };

export function getLibraryMap(libraries: readonly Library[]): LibraryMap {
return Object.fromEntries(libraries.map((library) => [getLibraryKey(library), library]));
Copy link
Member

Choose a reason for hiding this comment

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

not blocking but I wonder if we should just return some interface with get and set and leave the internal representation and key shape as implementation details, then you can use this almost like a regular map but with 2-3 args for the key (path + name + maybe deployer address)

holic
holic previously approved these changes Sep 19, 2024
Copy link
Member

@holic holic left a comment

Choose a reason for hiding this comment

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

lgtm!

main question is whether or not we need to key by deployer since we cache the address and the address is based on the deployer

Copy link
Member

@holic holic left a comment

Choose a reason for hiding this comment

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

nice! I dig the new LibraryMap

@holic holic merged commit 22c37c3 into main Sep 19, 2024
12 checks passed
@holic holic deleted the alvrs/prepare-deploy branch September 19, 2024 12:34
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.

2 participants