-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Cache the regularized form of union types #37749
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
Conversation
@typescript-bot perf test this |
Heya @weswigham, I've started to run the perf test suite on this PR at d702b0d. You can monitor the build here. Update: The results are in! |
@weswigham Here they are:Comparison Report - master..37749
System
Hosts
Scenarios
|
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.
Seems like a good idea. One thing though: Why didn't memory usage improve? Are our perf tests actually creating enough unions to test this?
@@ -0,0 +1,8012 @@ | |||
type BigUnion = | |||
{ | |||
name: '0'; |
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.
how long does does the original 1,000 take without the fix? 30 seconds post-fix is still pretty long to wait for a test to run.
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.
It's actually timing out on CI because it's taking more than 40 seconds there. I may need to either trim it a bit, or try to see what else is doing poorly and improve the perf more. 40 seconds for a single file with 6 statements and no control flow is still unreasonably bad, just not as unreasonable.
Oops, I meant, why doesn't memory usage increase, not improve. |
Why would memory usage change? Unions are globally cached so long as their members are the same, so the final type counts are inevitably the same. All that's different is we now keep a direct reference to the result on the union (saving us from needing to iterate over it more than once), which means the only addition is a property, which are allocated by the vm in blocks (rather than one at a time) anyway. |
The VM allocating properties in blocks was the missing piece in my understanding. Thanks. |
const sourceType = sourceTypes[i]; | ||
if (target.flags & TypeFlags.Union && (target as UnionType).types.length === sourceTypes.length) { | ||
// many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison | ||
const related = isRelatedTo(sourceType, (target as UnionType).types[i], /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); |
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.
Not sure I see why this would help. Union constituents are sorted by their type ids and those are pretty random even when one union is a mapping of the other. Am I missing something?
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.
Type IDs are assigned as they are made, and unions are sorted; ergo, unions are ordered by the order the types within them were created in - for a union that results from a mapping on another union, that means the resulting union will share the input union's order. We exploit this here by checking those corresponding elements against one another to make this common case linear in comparison time (when the result is assignable), rather than quadratic.
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.
This change is responsible for most of the performance improvements from the last commit, not the other one. The other one allows us to skip one silly quadratic comparison of a union to itself, while this is allowing us to compare BigUnion
to DiscriminateType<BigUnion, "name", T>
in a linear way (DiscriminateType
is a distributive conditional, so produces a union of types which are ultimately identical to BigUnion
, as T
is constrained to the values of "name"
).
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.
Right, in cases where the mapping creates new type IDs for each element I can see how this helps.
So my last commit takes the test runtime locally down from 30s to 5s, but CI is failing on |
@typescript-bot perf test this At least travis is green 😾 - hopefully perf can install and run. |
Heya @weswigham, I've started to run the perf test suite on this PR at cbf1b04. You can monitor the build here. Update: The results are in! |
@weswigham Here they are:Comparison Report - master..37749
System
Hosts
Scenarios
|
Nice improvement on material-ui check time! |
Fixes #37744
I doubled the example size for good measure. This simple change takes the test run down from 11 minutes to 30 seconds. That 30 seconds could likely still stand to be improved, but this fixes the obvious complexity issue.