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

refactor(common): cleanup some inferred types, add TS benchmark #2956

Merged
merged 5 commits into from
Jul 19, 2024

Conversation

ssalbdivad
Copy link
Collaborator

First of all, from what I can tell the last round of changes have had a huge positive improvement.

I haven't found any low-hanging fruit for reducing instantiation count so far other, but made some marginal type improvements along the way.

More importantly, after some iteration analyzing traces, I found a single change that could make a massive difference in check time despite not affecting instantiations. There's a type called SimulateContractReturnType in Viem that accepts 8 parameters and apparently has a complex enough stucture that it takes TS almost 2 seconds just to determine each parameter's variance.

With Variance Checks

image

However, with explicit annotations, these checks can be avoided altogether for external consumers. I added the annotations to viem's .d.ts in node_modules and saw a significant improvement in check time:

image

Now, checking the entire setupNetwork.ts file takes significantly less time (1.2s) than just those variance checks.

Unfortunately this isn't actionable in this PR, but I think it would make sense to follow up with a PR to Viem to add these annotations. The inferred annotations from TS can be seen in the first image above, although unfortunately independent (bivariant) can't be annotated directly.

@ssalbdivad ssalbdivad requested review from alvrs and holic as code owners July 18, 2024 07:33
Copy link

changeset-bot bot commented Jul 18, 2024

⚠️ No Changeset found

Latest commit: 7cc25c0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

}) as unknown as GetContractReturnType<TAbi, { public: TPublicClient; wallet: TWalletClient }, TAddress> & {
write: unknown;
};
}) as never;
Copy link
Member

@holic holic Jul 18, 2024

Choose a reason for hiding this comment

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

is there any practical benefit to this approach?

iirc we copied the as unknown as ... pattern from viem, so I wonder if there's some improvements to be done there too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, a few things:

  • Avoid duplicating long, complex expressions with no additional type safety
  • Avoid comparing potentially complex expressions that don't need to be compared because you're casting them anyways. This comparison shouldn't occur in the as unknown as X case but it would occur if e.g. you have an explicit return type and also case the return value in some areas to another complex expression instead of just never when the only goal is just to allow that return anyways.

Copy link
Member

Choose a reason for hiding this comment

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

happy with this change, but just FYI, I reverted this back to what's in main and I see no change in instantiations

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 should have been clearer, I made this change while optimizing perf based on the traces I saw but it just led me to the variance issue in viem. I know there is no perfect impact here, and specifically casting as unknown as X for a function with an explicit return type of X will not cost anything.

The problem is if X is a long expression, not only is it unnecessary duplication, if the two ever get slightly out of sync, e.g. an extra optional parameter is added to the return but not the cast, then TS does have to compare the instantiations which can be very expensive.

@@ -37,6 +37,7 @@
"package.json": "pnpm sort-package-json"
},
"devDependencies": {
"@ark/attest": "0.9.2",
Copy link
Member

Choose a reason for hiding this comment

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

I saw you did this in another PR and I had moved it out of the workspace root in a follow up PR to be consistent with how we've historically managed deps. Open to changing this approach though and curious to hear your thoughts - should common test deps be added to the workspace root instead of individual packages?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, devDependencies should generally be managed at the monorepo root in my opinion. It prevents having to manage multiple versions and continue to install them in new packages as needed, and there's really no downside as long as you're not importing them in source. I have an eslint rule that warns if I'm importing from a package not listed in dependencies.

Copy link
Member

Choose a reason for hiding this comment

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

cool, added a follow up here: #2962

});

return t;
}).types([20704, "instantiations"]);
Copy link
Member

Choose a reason for hiding this comment

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

I saw this 2x when using TS types from source as opposed to DTS. I wonder if we should have benchmarks for both cases?

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 saw this as well. The differences usually aren't so drastic for me but it makes sense in a repo like this where you have a lot of complex function returns etc, because it's checking your own .ts code as well.

The .d.ts test is going to be the more relevant for your users, but including paths would still give you something to iterate on so I think it could be reasonable to include both.

@holic holic changed the title perf(common): cleanup some inferred types, add vanilla template benchmark refactor(common): cleanup some inferred types, add TS benchmark Jul 19, 2024
@holic holic merged commit 6189540 into latticexyz:main Jul 19, 2024
13 of 14 checks passed
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