Skip to content

perf(compiler-cli): template type-checking performance improvements #38418

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

Closed
wants to merge 4 commits into from

Conversation

JoostK
Copy link
Member

@JoostK JoostK commented Aug 11, 2020

See individual commits.


I tested these changes in the ng-speed-rebuild repo, using Angular 10.1-next.5 and the changes in this PR, using roughly the following:

nvm use 12.16.1
git clone https://github.com/elvisbegovic/ng-speed-rebuild.git
cd ng-speed-rebuild
yarn
yarn ng update @angular/core @angular/cli --next --force --allow-dirty
# Install changes from PR
yarn ng build # Triggers ngcc
# Edit projects/one/tsconfig.app.json to enable "ivyTemplateTypeCheck", "fullTemplateTypeCheck" and "strictTemplates"
time ./node_modules/.bin/ngc -p projects/one/tsconfig.app.json

Note that the above configuration will actually cause compilation errors due to strictTemplates being enabled, which means that there's no code being emitted. The emit phase is expensive by itself (especially with source maps enabled) but not included in the below figures.

Prior to this PR, using 10.1-next.5, ngc took 75 seconds to execute. With the changes from this PR, it is reduced to 45 seconds; a 30 second reduction in time. The 30 second improvement is mostly from reduced type-checking time that TypeScript performs; from 35s to 7s. This is beneficial for incremental builds as well, given that type-checking is not done incrementally and has to be repeated from scratch on each incremental compilation. Side-note: the CLI performs incremental type-checking in a background process, so the effect of this may not be immediately visible (although your CPU will enjoy the extra free time!).


Thanks to @elvisbegovic for the awesome ng-speed-rebuild repo ❤️ Having these repo's available is super valuable in chasing down performance bottlenecks and experimenting with ways to address them.

@JoostK JoostK added state: WIP refactoring Issue that involves refactoring or code-cleanup area: compiler Issues related to `ngc`, Angular's template compiler labels Aug 11, 2020
@ngbot ngbot bot added this to the needsTriage milestone Aug 11, 2020
JoostK added 4 commits August 12, 2020 21:14
…elements

The template type-checker would generate a statement with a call
expression for all DOM elements in a template of the form:

```
const _t1 = document.createElement("div");
```

Profiling has shown that this is a particularly expensive call to
perform type inference on, as TypeScript needs to perform signature
selection of `Document.createElement` and resolve the exact type from
the `HTMLElementTagNameMap`. However, it can be observed that the
statement by itself does not contribute anything to the type-checking
result if `_t1` is not actually used anywhere, which is only rarely the
case---it requires that the element is referenced by its name from
somewhere else in the template. Consequently, the type-checker can skip
generating this statement altogether for most DOM elements.

The effect of this optimization is significant in several phases:
1. Less type-check code to generate
2. Less type-check code to emit and parse again
3. No expensive type inference to perform for the call expression

The effect on phase 3 is the most significant here, as type-checking is
not currently incremental in the sense that only phases 1 and 2 can
be reused from a prior compilation. The actual type-checking of all
templates in phase 3 needs to be repeated on each incremental
compilation, so any performance gains we achieve here are very
beneficial.
The template type-checker would always generate a directive declaration
even if its type was never used. For example, directives without any
input nor output bindings nor exportAs references don't need the
directive to be declared, as its type would never be used.

This commit makes the `TcbOp`s that are responsible for declaring a
directive as optional, such that they are only executed when requested
from another operation.
For a template that contains for example `<span *ngIf="first"></span>`
there's no need to render the `NgIf` guard expression, as the child
scope does not have any type-checking statements, so any narrowing
effect of the guard is not applicable.

This seems like a minor improvement, however it reduces the number of
flow-node antecedents that TypeScript needs to keep into account for
such cases, resulting in an overall reduction of type-checking time.
@JoostK JoostK added target: major This PR is targeted for the next major release and removed state: WIP labels Aug 12, 2020
@JoostK JoostK marked this pull request as ready for review August 12, 2020 19:48
@JoostK JoostK added action: merge The PR is ready for merge by the caretaker action: presubmit The PR is in need of a google3 presubmit labels Aug 12, 2020
@atscott atscott removed the action: presubmit The PR is in need of a google3 presubmit label Aug 13, 2020
@atscott atscott closed this in f42e6ce Aug 13, 2020
atscott pushed a commit that referenced this pull request Aug 13, 2020
…38418)

The template type-checker would always generate a directive declaration
even if its type was never used. For example, directives without any
input nor output bindings nor exportAs references don't need the
directive to be declared, as its type would never be used.

This commit makes the `TcbOp`s that are responsible for declaring a
directive as optional, such that they are only executed when requested
from another operation.

PR Close #38418
atscott pushed a commit that referenced this pull request Aug 13, 2020
…pty (#38418)

For a template that contains for example `<span *ngIf="first"></span>`
there's no need to render the `NgIf` guard expression, as the child
scope does not have any type-checking statements, so any narrowing
effect of the guard is not applicable.

This seems like a minor improvement, however it reduces the number of
flow-node antecedents that TypeScript needs to keep into account for
such cases, resulting in an overall reduction of type-checking time.

PR Close #38418
profanis pushed a commit to profanis/angular that referenced this pull request Sep 5, 2020
…elements (angular#38418)

The template type-checker would generate a statement with a call
expression for all DOM elements in a template of the form:

```
const _t1 = document.createElement("div");
```

Profiling has shown that this is a particularly expensive call to
perform type inference on, as TypeScript needs to perform signature
selection of `Document.createElement` and resolve the exact type from
the `HTMLElementTagNameMap`. However, it can be observed that the
statement by itself does not contribute anything to the type-checking
result if `_t1` is not actually used anywhere, which is only rarely the
case---it requires that the element is referenced by its name from
somewhere else in the template. Consequently, the type-checker can skip
generating this statement altogether for most DOM elements.

The effect of this optimization is significant in several phases:
1. Less type-check code to generate
2. Less type-check code to emit and parse again
3. No expensive type inference to perform for the call expression

The effect on phase 3 is the most significant here, as type-checking is
not currently incremental in the sense that only phases 1 and 2 can
be reused from a prior compilation. The actual type-checking of all
templates in phase 3 needs to be repeated on each incremental
compilation, so any performance gains we achieve here are very
beneficial.

PR Close angular#38418
profanis pushed a commit to profanis/angular that referenced this pull request Sep 5, 2020
…ngular#38418)

The template type-checker would always generate a directive declaration
even if its type was never used. For example, directives without any
input nor output bindings nor exportAs references don't need the
directive to be declared, as its type would never be used.

This commit makes the `TcbOp`s that are responsible for declaring a
directive as optional, such that they are only executed when requested
from another operation.

PR Close angular#38418
profanis pushed a commit to profanis/angular that referenced this pull request Sep 5, 2020
…pty (angular#38418)

For a template that contains for example `<span *ngIf="first"></span>`
there's no need to render the `NgIf` guard expression, as the child
scope does not have any type-checking statements, so any narrowing
effect of the guard is not applicable.

This seems like a minor improvement, however it reduces the number of
flow-node antecedents that TypeScript needs to keep into account for
such cases, resulting in an overall reduction of type-checking time.

PR Close angular#38418
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: compiler Issues related to `ngc`, Angular's template compiler cla: yes refactoring Issue that involves refactoring or code-cleanup target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants