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

When calculating spreads, merge empty object into nonempty object to … #34853

Merged
merged 4 commits into from
Nov 23, 2019

Conversation

weswigham
Copy link
Member

…produce a partial object to reduce complexity of the calculation. This special-cases scenarios like repeated spreads of false | ObjType or ObjType | undefined so they produce a single type (rather than a union of types_ that we can operate on. Specifically, in this PR, we recognize unions similar to {} | {a: string} when spreading, and interpret them as {a?: string} instead (essentially moving the outer union to an inner union, though the constraint is a bit stronger than a nonfresh {} would normally imply). Strictly speaking, this reduces the accuracy of the type logic a bit, but in exchange, improves its performance in pathological scenarios like those described in the linked issue.

To some up, while previously, {} | {a: string} spread with {} | {b: string} would produce {} | {a: string} | {b: string} | {a: string; b: string}, now it produces just {a?: string; b?: string;} instead.

Fixes #34599

@weswigham
Copy link
Member Author

@typescript-bot user test this
@typescript-bot test this
@typescript-bot perf test this

Prooobably not much use in it, but also
@typescript-bot run dt

@typescript-bot
Copy link
Collaborator

typescript-bot commented Oct 31, 2019

Heya @weswigham, I've started to run the perf test suite on this PR at 8957dfe. You can monitor the build here. It should now contribute to this PR's status checks.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Oct 31, 2019

Heya @weswigham, I've started to run the parallelized Definitely Typed test suite on this PR at 8957dfe. You can monitor the build here. It should now contribute to this PR's status checks.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Oct 31, 2019

Heya @weswigham, I've started to run the extended test suite on this PR at 8957dfe. You can monitor the build here. It should now contribute to this PR's status checks.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Oct 31, 2019

Heya @weswigham, I've started to run the parallelized community code test suite on this PR at 8957dfe. You can monitor the build here. It should now contribute to this PR's status checks.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

I'm surprised we got away with this as long as we did. Just some follow-up questions.

}

function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): Type | undefined {
if (type.types.length === 2) {
Copy link
Member

Choose a reason for hiding this comment

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

why only cover a pair of types? is that just the most common case?

Copy link
Member Author

Choose a reason for hiding this comment

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

Pretty much. {} | Whatever being roughly equivalent to Partial<whatever> is fairly obvious - {} | Foo | Bar is... less obvious?

return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType;
}
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType)) {
return getAnonymousPartialType(secondType);
Copy link
Member

Choose a reason for hiding this comment

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

why can't we construct a real partial type here? Probably because it would just be expensive and might not exist, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is just doing the spread operation (skipping privates, only taking spreadable props) here, but also mixing in the Optional flag. I could map it thru partial then feed it back out thru this process, but that's a lot of extra steps (that can fail) for no real gain.

if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) {
// do nothing, skip privates
}
else if (isSpreadableProperty(prop)) {
Copy link
Member

Choose a reason for hiding this comment

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

should isSpreadableProperty exclude private/protected instead of needing a separate check?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope - this is pretty much a clone of the property copying code farther down in getSpreadType, but I don't need to bother collecting skippedPrivateMembers (the other type is already known to be empty), and I set the Optional property on every property.

@typescript-bot
Copy link
Collaborator

@weswigham
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..34853

Metric master 34853 Delta Best Worst
Angular - node (v10.16.3, x64)
Memory used 355,183k (± 0.01%) 356,907k (± 0.01%) +1,724k (+ 0.49%) 356,797k 356,965k
Parse Time 1.64s (± 0.47%) 1.64s (± 0.68%) -0.00s (- 0.18%) 1.62s 1.67s
Bind Time 0.86s (± 0.72%) 0.88s (± 0.78%) +0.02s (+ 1.74%) 0.86s 0.89s
Check Time 4.57s (± 0.84%) 4.54s (± 0.49%) -0.03s (- 0.72%) 4.49s 4.60s
Emit Time 5.29s (± 0.58%) 5.28s (± 0.86%) -0.01s (- 0.17%) 5.19s 5.42s
Total Time 12.36s (± 0.49%) 12.33s (± 0.41%) -0.03s (- 0.26%) 12.23s 12.46s
Monaco - node (v10.16.3, x64)
Memory used 365,953k (± 0.02%) 366,017k (± 0.01%) +64k (+ 0.02%) 365,849k 366,129k
Parse Time 1.26s (± 0.75%) 1.26s (± 0.54%) 0.00s ( 0.00%) 1.25s 1.28s
Bind Time 0.76s (± 0.48%) 0.76s (± 0.73%) +0.00s (+ 0.26%) 0.75s 0.77s
Check Time 4.67s (± 0.61%) 4.67s (± 0.42%) -0.00s (- 0.06%) 4.62s 4.71s
Emit Time 2.95s (± 0.80%) 2.96s (± 0.64%) +0.01s (+ 0.24%) 2.93s 3.02s
Total Time 9.64s (± 0.59%) 9.65s (± 0.26%) +0.01s (+ 0.08%) 9.59s 9.70s
TFS - node (v10.16.3, x64)
Memory used 321,648k (± 0.02%) 321,670k (± 0.02%) +22k (+ 0.01%) 321,579k 321,823k
Parse Time 0.96s (± 0.77%) 0.96s (± 0.69%) +0.00s (+ 0.31%) 0.95s 0.98s
Bind Time 0.72s (± 1.36%) 0.72s (± 1.56%) -0.00s (- 0.42%) 0.69s 0.74s
Check Time 4.12s (± 0.55%) 4.13s (± 0.44%) +0.01s (+ 0.24%) 4.08s 4.17s
Emit Time 3.06s (± 0.67%) 3.08s (± 0.86%) +0.02s (+ 0.56%) 2.98s 3.11s
Total Time 8.86s (± 0.37%) 8.89s (± 0.33%) +0.03s (+ 0.29%) 8.81s 8.94s
Angular - node (v12.1.0, x64)
Memory used 330,557k (± 0.10%) 332,371k (± 0.02%) +1,815k (+ 0.55%) 332,189k 332,508k
Parse Time 1.57s (± 0.52%) 1.58s (± 0.60%) +0.01s (+ 0.89%) 1.57s 1.61s
Bind Time 0.85s (± 0.40%) 0.86s (± 0.80%) +0.01s (+ 0.94%) 0.84s 0.87s
Check Time 4.49s (± 0.95%) 4.45s (± 0.52%) -0.04s (- 0.85%) 4.38s 4.51s
Emit Time 5.49s (± 0.86%) 5.45s (± 0.68%) -0.04s (- 0.80%) 5.35s 5.51s
Total Time 12.39s (± 0.67%) 12.34s (± 0.46%) -0.06s (- 0.45%) 12.16s 12.45s
Monaco - node (v12.1.0, x64)
Memory used 345,766k (± 0.03%) 345,732k (± 0.02%) -34k (- 0.01%) 345,563k 345,862k
Parse Time 1.23s (± 0.57%) 1.23s (± 0.91%) +0.00s (+ 0.16%) 1.20s 1.25s
Bind Time 0.73s (± 1.42%) 0.73s (± 0.91%) -0.00s (- 0.27%) 0.72s 0.75s
Check Time 4.51s (± 0.33%) 4.49s (± 0.46%) -0.03s (- 0.62%) 4.44s 4.55s
Emit Time 3.01s (± 0.73%) 3.02s (± 0.59%) +0.01s (+ 0.37%) 2.98s 3.06s
Total Time 9.48s (± 0.36%) 9.46s (± 0.43%) -0.02s (- 0.20%) 9.35s 9.57s
TFS - node (v12.1.0, x64)
Memory used 304,038k (± 0.02%) 304,070k (± 0.01%) +32k (+ 0.01%) 303,974k 304,170k
Parse Time 0.95s (± 0.80%) 0.95s (± 0.47%) +0.01s (+ 0.63%) 0.94s 0.96s
Bind Time 0.69s (± 1.02%) 0.69s (± 0.90%) 0.00s ( 0.00%) 0.68s 0.70s
Check Time 4.06s (± 0.23%) 4.05s (± 0.48%) -0.01s (- 0.27%) 4.01s 4.08s
Emit Time 3.11s (± 0.91%) 3.12s (± 1.94%) +0.01s (+ 0.35%) 3.01s 3.33s
Total Time 8.81s (± 0.30%) 8.82s (± 0.64%) +0.01s (+ 0.10%) 8.71s 8.99s
Angular - node (v8.9.0, x64)
Memory used 349,922k (± 0.02%) 351,584k (± 0.02%) +1,663k (+ 0.48%) 351,387k 351,698k
Parse Time 2.11s (± 0.39%) 2.13s (± 0.64%) +0.02s (+ 0.71%) 2.10s 2.16s
Bind Time 0.91s (± 0.44%) 0.92s (± 0.64%) +0.01s (+ 1.43%) 0.91s 0.94s
Check Time 5.31s (± 0.56%) 5.33s (± 0.63%) +0.01s (+ 0.23%) 5.27s 5.43s
Emit Time 6.30s (± 1.06%) 6.29s (± 0.81%) -0.02s (- 0.25%) 6.11s 6.37s
Total Time 14.64s (± 0.49%) 14.67s (± 0.50%) +0.02s (+ 0.16%) 14.45s 14.79s
Monaco - node (v8.9.0, x64)
Memory used 363,795k (± 0.01%) 363,805k (± 0.01%) +10k (+ 0.00%) 363,743k 363,888k
Parse Time 1.57s (± 0.23%) 1.57s (± 0.32%) -0.00s (- 0.06%) 1.56s 1.58s
Bind Time 0.92s (± 0.94%) 0.93s (± 0.65%) +0.00s (+ 0.33%) 0.91s 0.94s
Check Time 5.54s (± 0.63%) 5.53s (± 0.37%) -0.02s (- 0.32%) 5.46s 5.56s
Emit Time 3.05s (± 0.83%) 3.04s (± 1.02%) -0.00s (- 0.16%) 2.94s 3.10s
Total Time 11.08s (± 0.38%) 11.06s (± 0.35%) -0.02s (- 0.22%) 10.96s 11.14s
TFS - node (v8.9.0, x64)
Memory used 320,535k (± 0.01%) 320,557k (± 0.01%) +22k (+ 0.01%) 320,450k 320,627k
Parse Time 1.27s (± 0.39%) 1.27s (± 0.63%) -0.00s (- 0.24%) 1.25s 1.29s
Bind Time 0.74s (± 0.60%) 0.74s (± 0.70%) -0.00s (- 0.40%) 0.73s 0.75s
Check Time 4.73s (± 0.40%) 4.72s (± 0.52%) -0.00s (- 0.08%) 4.64s 4.75s
Emit Time 3.23s (± 0.91%) 3.23s (± 0.73%) +0.01s (+ 0.19%) 3.18s 3.30s
Total Time 9.98s (± 0.42%) 9.97s (± 0.38%) -0.00s (- 0.04%) 9.87s 10.04s
Angular - node (v8.9.0, x86)
Memory used 198,696k (± 0.02%) 199,575k (± 0.01%) +880k (+ 0.44%) 199,533k 199,640k
Parse Time 2.04s (± 0.90%) 2.05s (± 0.71%) +0.02s (+ 0.88%) 2.02s 2.08s
Bind Time 1.03s (± 0.71%) 1.03s (± 0.80%) -0.01s (- 0.48%) 1.01s 1.04s
Check Time 4.83s (± 0.69%) 4.80s (± 0.46%) -0.03s (- 0.58%) 4.75s 4.83s
Emit Time 6.07s (± 0.89%) 6.15s (± 2.05%) +0.07s (+ 1.22%) 5.92s 6.42s
Total Time 13.96s (± 0.42%) 14.02s (± 1.01%) +0.06s (+ 0.42%) 13.75s 14.33s
Monaco - node (v8.9.0, x86)
Memory used 203,743k (± 0.02%) 203,786k (± 0.02%) +43k (+ 0.02%) 203,690k 203,841k
Parse Time 1.62s (± 0.51%) 1.62s (± 0.86%) +0.00s (+ 0.25%) 1.59s 1.66s
Bind Time 0.75s (± 0.77%) 0.75s (± 0.69%) +0.00s (+ 0.13%) 0.74s 0.76s
Check Time 5.38s (± 0.45%) 5.41s (± 0.80%) +0.03s (+ 0.52%) 5.32s 5.55s
Emit Time 2.90s (± 1.34%) 2.90s (± 1.62%) -0.00s (- 0.14%) 2.84s 3.04s
Total Time 10.65s (± 0.32%) 10.68s (± 0.63%) +0.03s (+ 0.31%) 10.57s 10.90s
TFS - node (v8.9.0, x86)
Memory used 180,563k (± 0.02%) 180,571k (± 0.01%) +8k (+ 0.00%) 180,530k 180,644k
Parse Time 1.32s (± 0.62%) 1.31s (± 0.52%) -0.00s (- 0.23%) 1.30s 1.33s
Bind Time 0.70s (± 0.68%) 0.70s (± 1.17%) 0.00s ( 0.00%) 0.68s 0.72s
Check Time 4.50s (± 0.64%) 4.49s (± 0.83%) -0.00s (- 0.09%) 4.41s 4.55s
Emit Time 2.98s (± 1.38%) 2.98s (± 0.52%) +0.01s (+ 0.20%) 2.93s 3.00s
Total Time 9.49s (± 0.65%) 9.48s (± 0.48%) -0.00s (- 0.03%) 9.39s 9.57s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-166-generic
Architecturex64
Available Memory16 GB
Available Memory8 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v10.16.3, x64)
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
Scenarios
  • Angular - node (v10.16.3, x64)
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Monaco - node (v10.16.3, x64)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • TFS - node (v10.16.3, x64)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
Benchmark Name Iterations
Current 34853 10
Baseline master 10

@weswigham
Copy link
Member Author

Everything's green - DT's failure's are master's DT failures (surely protractor will update some day).

@weswigham weswigham requested a review from sandersn November 2, 2019 00:02
@weswigham
Copy link
Member Author

@sandersn @ahejlsberg you wanna drop by for a code review so we can get this merged? 😄

@sandersn
Copy link
Member

The code still looks good to me. I can't remember if we decided to aim for more accuracy in the design meeting. The notes don't say anything but I remember something to that effect being tossed out.

@weswigham
Copy link
Member Author

@sandersn IIRC the request was to limit the simplification case to only when the type being merged in has one property, to limit the potential area it affects, which this now does.

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.

[Performance] repeated property spreading causes out of memory error
4 participants