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

Improve control flow type caching #35332

Merged
merged 3 commits into from
Nov 25, 2019
Merged

Improve control flow type caching #35332

merged 3 commits into from
Nov 25, 2019

Conversation

ahejlsberg
Copy link
Member

This PR improves performance of getTypeOfExpression by caching results whenever control flow analysis was required to compute them. Unlike our existing caching schemes, this implementation works even during control flow analysis of loops: The cache is saved and cleared before starting analysis of a loop back edge (which may produce incomplete types), and then restored when analysis completes.

Fixes #35205.

@ahejlsberg
Copy link
Member Author

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 25, 2019

Heya @ahejlsberg, I've started to run the perf test suite on this PR at 2d19e83. 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 Nov 25, 2019

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 25, 2019

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 25, 2019

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

@ahejlsberg ahejlsberg requested a review from weswigham November 25, 2019 17:29
@typescript-bot
Copy link
Collaborator

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

Here they are:

Comparison Report - master..35332

Metric master 35332 Delta Best Worst
Angular - node (v10.16.3, x64)
Memory used 354,766k (± 0.02%) 356,534k (± 0.01%) +1,768k (+ 0.50%) 356,470k 356,579k
Parse Time 1.61s (± 0.69%) 1.63s (± 0.61%) +0.02s (+ 0.99%) 1.60s 1.65s
Bind Time 0.86s (± 0.78%) 0.87s (± 0.86%) +0.01s (+ 1.17%) 0.85s 0.88s
Check Time 4.54s (± 0.50%) 4.53s (± 0.53%) -0.01s (- 0.22%) 4.46s 4.58s
Emit Time 5.25s (± 0.68%) 5.28s (± 0.99%) +0.02s (+ 0.46%) 5.14s 5.37s
Total Time 12.26s (± 0.24%) 12.30s (± 0.40%) +0.04s (+ 0.36%) 12.15s 12.37s
Monaco - node (v10.16.3, x64)
Memory used 365,965k (± 0.01%) 366,089k (± 0.01%) +123k (+ 0.03%) 365,948k 366,204k
Parse Time 1.25s (± 0.53%) 1.26s (± 0.78%) +0.01s (+ 0.40%) 1.24s 1.28s
Bind Time 0.75s (± 0.99%) 0.76s (± 0.77%) +0.00s (+ 0.53%) 0.75s 0.77s
Check Time 4.65s (± 0.56%) 4.65s (± 0.59%) -0.00s (- 0.06%) 4.58s 4.70s
Emit Time 2.96s (± 0.72%) 2.96s (± 0.81%) +0.00s (+ 0.03%) 2.90s 3.00s
Total Time 9.62s (± 0.43%) 9.62s (± 0.50%) +0.00s (+ 0.04%) 9.49s 9.73s
TFS - node (v10.16.3, x64)
Memory used 321,659k (± 0.02%) 321,943k (± 0.01%) +284k (+ 0.09%) 321,885k 322,057k
Parse Time 0.96s (± 0.94%) 0.96s (± 0.76%) -0.00s (- 0.31%) 0.94s 0.97s
Bind Time 0.73s (± 1.46%) 0.72s (± 1.65%) -0.01s (- 1.10%) 0.70s 0.75s
Check Time 4.12s (± 0.67%) 4.12s (± 0.64%) -0.00s (- 0.12%) 4.06s 4.17s
Emit Time 3.05s (± 0.97%) 3.07s (± 0.60%) +0.02s (+ 0.52%) 3.02s 3.10s
Total Time 8.86s (± 0.74%) 8.86s (± 0.39%) +0.00s (+ 0.01%) 8.79s 8.93s
Angular - node (v12.1.0, x64)
Memory used 330,231k (± 0.06%) 332,048k (± 0.02%) +1,817k (+ 0.55%) 331,908k 332,141k
Parse Time 1.56s (± 0.55%) 1.58s (± 0.89%) +0.02s (+ 1.28%) 1.56s 1.63s
Bind Time 0.84s (± 0.81%) 0.85s (± 1.09%) +0.01s (+ 0.95%) 0.83s 0.87s
Check Time 4.47s (± 0.70%) 4.44s (± 0.32%) -0.03s (- 0.74%) 4.41s 4.47s
Emit Time 5.50s (± 0.91%) 5.48s (± 0.75%) -0.02s (- 0.36%) 5.42s 5.60s
Total Time 12.38s (± 0.60%) 12.35s (± 0.36%) -0.03s (- 0.22%) 12.26s 12.44s
Monaco - node (v12.1.0, x64)
Memory used 345,687k (± 0.02%) 345,760k (± 0.02%) +73k (+ 0.02%) 345,640k 345,933k
Parse Time 1.23s (± 0.86%) 1.23s (± 0.61%) +0.01s (+ 0.57%) 1.22s 1.25s
Bind Time 0.73s (± 0.96%) 0.72s (± 0.94%) -0.00s (- 0.69%) 0.71s 0.74s
Check Time 4.51s (± 0.48%) 4.49s (± 0.30%) -0.02s (- 0.38%) 4.46s 4.52s
Emit Time 3.01s (± 0.85%) 3.01s (± 0.88%) +0.00s (+ 0.10%) 2.97s 3.09s
Total Time 9.47s (± 0.50%) 9.46s (± 0.34%) -0.01s (- 0.08%) 9.40s 9.56s
TFS - node (v12.1.0, x64)
Memory used 304,076k (± 0.01%) 304,321k (± 0.02%) +245k (+ 0.08%) 304,196k 304,457k
Parse Time 0.95s (± 0.89%) 0.95s (± 0.74%) +0.00s (+ 0.00%) 0.94s 0.96s
Bind Time 0.68s (± 0.98%) 0.69s (± 0.69%) +0.00s (+ 0.59%) 0.68s 0.70s
Check Time 4.05s (± 0.36%) 4.06s (± 0.52%) +0.01s (+ 0.15%) 3.99s 4.08s
Emit Time 3.12s (± 2.07%) 3.10s (± 0.76%) -0.01s (- 0.45%) 3.07s 3.17s
Total Time 8.80s (± 0.79%) 8.80s (± 0.40%) -0.01s (- 0.07%) 8.70s 8.86s
Angular - node (v8.9.0, x64)
Memory used 349,629k (± 0.01%) 351,325k (± 0.01%) +1,696k (+ 0.49%) 351,275k 351,406k
Parse Time 2.11s (± 0.31%) 2.12s (± 0.59%) +0.01s (+ 0.38%) 2.09s 2.15s
Bind Time 0.92s (± 1.22%) 0.92s (± 0.89%) +0.00s (+ 0.22%) 0.90s 0.94s
Check Time 5.29s (± 0.90%) 5.27s (± 1.04%) -0.02s (- 0.36%) 5.16s 5.42s
Emit Time 6.21s (± 1.07%) 6.27s (± 1.49%) +0.06s (+ 1.03%) 5.95s 6.44s
Total Time 14.52s (± 0.71%) 14.58s (± 0.68%) +0.06s (+ 0.40%) 14.42s 14.85s
Monaco - node (v8.9.0, x64)
Memory used 363,767k (± 0.01%) 363,877k (± 0.02%) +110k (+ 0.03%) 363,754k 363,989k
Parse Time 1.56s (± 0.47%) 1.56s (± 0.65%) +0.00s (+ 0.19%) 1.54s 1.59s
Bind Time 0.93s (± 0.86%) 0.93s (± 0.71%) -0.00s (- 0.21%) 0.92s 0.94s
Check Time 5.58s (± 0.51%) 5.54s (± 1.04%) -0.04s (- 0.75%) 5.34s 5.64s
Emit Time 3.07s (± 0.31%) 3.10s (± 3.26%) +0.03s (+ 0.85%) 3.02s 3.50s
Total Time 11.14s (± 0.36%) 11.13s (± 0.65%) -0.01s (- 0.13%) 10.98s 11.35s
TFS - node (v8.9.0, x64)
Memory used 320,625k (± 0.01%) 320,894k (± 0.01%) +269k (+ 0.08%) 320,828k 320,969k
Parse Time 1.27s (± 0.65%) 1.27s (± 0.76%) -0.01s (- 0.39%) 1.25s 1.29s
Bind Time 0.74s (± 1.02%) 0.74s (± 0.49%) +0.00s (+ 0.00%) 0.74s 0.75s
Check Time 4.75s (± 0.36%) 4.75s (± 0.34%) -0.00s (- 0.04%) 4.70s 4.78s
Emit Time 3.23s (± 0.70%) 3.22s (± 0.89%) -0.00s (- 0.12%) 3.13s 3.29s
Total Time 10.00s (± 0.29%) 9.98s (± 0.36%) -0.01s (- 0.13%) 9.90s 10.05s
Angular - node (v8.9.0, x86)
Memory used 198,538k (± 0.02%) 199,433k (± 0.02%) +896k (+ 0.45%) 199,323k 199,541k
Parse Time 2.02s (± 0.89%) 2.04s (± 0.95%) +0.02s (+ 0.99%) 2.01s 2.10s
Bind Time 1.03s (± 1.31%) 1.02s (± 0.46%) -0.01s (- 0.87%) 1.01s 1.03s
Check Time 4.83s (± 0.71%) 4.80s (± 0.71%) -0.03s (- 0.68%) 4.73s 4.89s
Emit Time 6.12s (± 1.86%) 6.08s (± 1.07%) -0.04s (- 0.64%) 5.96s 6.29s
Total Time 14.01s (± 0.88%) 13.94s (± 0.60%) -0.06s (- 0.44%) 13.79s 14.17s
Monaco - node (v8.9.0, x86)
Memory used 203,760k (± 0.02%) 203,845k (± 0.02%) +85k (+ 0.04%) 203,744k 203,981k
Parse Time 1.60s (± 0.57%) 1.61s (± 0.47%) +0.01s (+ 0.31%) 1.59s 1.63s
Bind Time 0.75s (± 1.13%) 0.75s (± 0.64%) -0.00s (- 0.27%) 0.74s 0.76s
Check Time 5.42s (± 0.52%) 5.40s (± 0.79%) -0.02s (- 0.35%) 5.30s 5.49s
Emit Time 2.87s (± 0.57%) 2.94s (± 1.75%) +0.07s (+ 2.51%) 2.85s 3.03s
Total Time 10.64s (± 0.32%) 10.70s (± 0.40%) +0.06s (+ 0.54%) 10.62s 10.81s
TFS - node (v8.9.0, x86)
Memory used 180,640k (± 0.02%) 180,857k (± 0.01%) +217k (+ 0.12%) 180,806k 180,910k
Parse Time 1.31s (± 0.87%) 1.31s (± 0.74%) -0.00s (- 0.08%) 1.29s 1.33s
Bind Time 0.69s (± 0.53%) 0.70s (± 1.78%) +0.01s (+ 1.29%) 0.69s 0.75s
Check Time 4.48s (± 0.58%) 4.49s (± 0.59%) +0.01s (+ 0.34%) 4.43s 4.55s
Emit Time 2.96s (± 0.65%) 2.99s (± 0.77%) +0.03s (+ 0.88%) 2.94s 3.03s
Total Time 9.45s (± 0.36%) 9.50s (± 0.47%) +0.05s (+ 0.54%) 9.42s 9.59s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-166-generic
Architecturex64
Available Memory16 GB
Available Memory6 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 35332 10
Baseline master 10

@typescript-bot
Copy link
Collaborator

The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master.

flowLoopStart = flowLoopCount;
flowTypeCache = undefined;
Copy link
Member

Choose a reason for hiding this comment

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

Since we do it in two places, can we capture this "execute in new flow cache context" pattern into it's own helper function?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it is fine as is.

@@ -27332,7 +27324,7 @@ namespace ts {

function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
const initializer = getEffectiveInitializer(declaration)!;
const type = getTypeOfExpression(initializer, /*cache*/ true);
const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer);
Copy link
Member

Choose a reason for hiding this comment

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

IIRC, using the cache flag here was due to a perf issue, not a correctness one, and the caching was a correctness trade-off - using the per-loop cache is probably better, if possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, we need to call checkExpressionCached here because it resets the flow loop state to ensure everything is computed from scratch before the type is permanently cached. The getTypeOfExpression cache is purely there to help performance, but there is no guarantee that the same type will be returned every time.

@ahejlsberg
Copy link
Member Author

Tests look good (all errors are preexisting conditions) and performance shows a slight positive effect.

*/
function getTypeOfExpression(node: Expression, cache?: boolean) {
function getTypeOfExpression(node: Expression) {
Copy link
Member

Choose a reason for hiding this comment

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

We should document that we probably only expect this to be called into from flow analysis now - using it elsewhere is probably a mistake. I know we have a call into it from the node builder and services layers, too, but both of those should probably just be using checkExpressionCached.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

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

Seems good-ish. That we're completely disabling the cache (and not using a temporary cache for the duration of the loop analysis) makes me wonder if something like

while (true) {
    while (true) {
        if (choice === choiceOne) {}
    }
}

would still have bad perf by avoiding the cache on the inner loop...

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.

perf: exponential slowdown due to type narrowing with enums / type unions
3 participants