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

perf(common): code size reduction of ngFor directive #44315

Closed
wants to merge 1 commit into from

Conversation

JoostK
Copy link
Member

@JoostK JoostK commented Nov 30, 2021

This commit makes several changes to the implementation of NgForOf to
reduce its code size in production builds:

  1. The tailor-made message for an unsupported differ is fully
    tree-shaken in production builds, in favor of the exception from the
    differ factory itself.
  2. The private _perViewChange method was changed into a free-standing
    function, to allow its name to be minimized.
  3. The need for an intermediate RecordViewTuple was avoided by
    applying the operation in-place, instead of collection all insertions
    into a buffer first. This is safe as the _perViewChange operation
    that used to be done on each RecordViewTuple is entirely local to
    the tuple itself. Hence, it is invariant to execution ordering which
    means that the _perViewChange can be executed directly during the
    forEachOperation loop.

@JoostK JoostK added area: common Issues related to APIs in the @angular/common package target: patch This PR is targeted for the next patch release labels Nov 30, 2021
@ngbot ngbot bot added this to the Backlog milestone Nov 30, 2021
@google-cla google-cla bot added the cla: yes label Nov 30, 2021
@JoostK
Copy link
Member Author

JoostK commented Nov 30, 2021

Note: the total size savings for the todo example app is 395 bytes. The removal of the insertion tuples buffer also reduces runtime memory pressure.

Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

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

This PR is in the draft mode (possibly in the "work in progress" state), but I wanted to leave a quick comment with an additional idea anyways :)

@@ -234,42 +237,32 @@ export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCh
const view = this._viewContainer.createEmbeddedView(
this._template, new NgForOfContext<T, U>(null!, this._ngForOf!, -1, -1),
currentIndex === null ? undefined : currentIndex);
const tuple = new RecordViewTuple<T, U>(item, view);
insertTuples.push(tuple);
applyViewChange(view, item);
} else if (currentIndex == null) {
this._viewContainer.remove(
adjustedPreviousIndex === null ? undefined : adjustedPreviousIndex);
} else if (adjustedPreviousIndex !== null) {
const view = this._viewContainer.get(adjustedPreviousIndex)!;
this._viewContainer.move(view, currentIndex);
Copy link
Contributor

Choose a reason for hiding this comment

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

The changes look great, thanks @JoostK 👍

One more idea that we can explore is to extract the this._viewContainer into a local const, so it's better minified:

const viewContainer = this._viewContainer;
// ...
viewContainer.move(view, currentIndex);

would be minified to something like this:

var v = this._viewContainer;
// ...
v.move(view, currentIndex);

and given the fact that we have 7 instances of this._viewContainer inside the function - we'd get more savings.

Also, that may be a tiny perf runtime improvement as well, since we'd avoid extra property reads.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks Andrew, that's an excellent idea!

@JoostK
Copy link
Member Author

JoostK commented Dec 1, 2021

Cool, we're now at 497 fewer bytes! I'll see if I can get the tests green (mostly golden updates) before marking this for review.

@JoostK JoostK marked this pull request as ready for review December 1, 2021 16:44
@JoostK JoostK added the action: review The PR is still awaiting reviews from at least one requested reviewer label Dec 1, 2021
Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

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

@JoostK thanks for the perf improvement 👍

packages/common/src/directives/ng_for_of.ts Show resolved Hide resolved
@AndrewKushnir AndrewKushnir removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Dec 1, 2021
@AndrewKushnir
Copy link
Contributor

Presubmit.

@AndrewKushnir AndrewKushnir added action: merge The PR is ready for merge by the caretaker merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note and removed action: presubmit The PR is in need of a google3 presubmit labels Dec 1, 2021
Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

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

Reviewed-for: size-tracking

@AndrewKushnir
Copy link
Contributor

Merge assistance: presubmits for this change are successful.

Copy link
Contributor

@jessicajaniuk jessicajaniuk left a comment

Choose a reason for hiding this comment

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

LGTM 🍪

reviewed-for: size-tracking

This commit makes several changes to the implementation of `NgForOf` to
reduce its code size in production builds:

1. The tailor-made message for an unsupported differ is fully
   tree-shaken in production builds, in favor of the exception from the
   differ factory itself.
2. The private `_perViewChange` method was changed into a free-standing
   function, to allow its name to be minimized.
3. The need for an intermediate `RecordViewTuple` was avoided by
   applying the operation in-place, instead of collecting all insertions
   into a buffer first. This is safe as the `_perViewChange` operation
   that used to be done on each `RecordViewTuple` is entirely local to
   the tuple itself. Hence, it is invariant to execution ordering which
   means that the `_perViewChange` can be executed directly during the
   `forEachOperation` loop.
@AndrewKushnir
Copy link
Contributor

FYI, there was a problem merging this PR, so I did a rebase. Will try to merge it again once CI is completed.

@AndrewKushnir AndrewKushnir removed the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Dec 2, 2021
@AndrewKushnir
Copy link
Contributor

This PR was merged into the repository by commit 1336297.

AndrewKushnir pushed a commit that referenced this pull request Dec 2, 2021
This commit makes several changes to the implementation of `NgForOf` to
reduce its code size in production builds:

1. The tailor-made message for an unsupported differ is fully
   tree-shaken in production builds, in favor of the exception from the
   differ factory itself.
2. The private `_perViewChange` method was changed into a free-standing
   function, to allow its name to be minimized.
3. The need for an intermediate `RecordViewTuple` was avoided by
   applying the operation in-place, instead of collecting all insertions
   into a buffer first. This is safe as the `_perViewChange` operation
   that used to be done on each `RecordViewTuple` is entirely local to
   the tuple itself. Hence, it is invariant to execution ordering which
   means that the `_perViewChange` can be executed directly during the
   `forEachOperation` loop.

PR Close #44315
dimakuba pushed a commit to dimakuba/angular that referenced this pull request Dec 28, 2021
This commit makes several changes to the implementation of `NgForOf` to
reduce its code size in production builds:

1. The tailor-made message for an unsupported differ is fully
   tree-shaken in production builds, in favor of the exception from the
   differ factory itself.
2. The private `_perViewChange` method was changed into a free-standing
   function, to allow its name to be minimized.
3. The need for an intermediate `RecordViewTuple` was avoided by
   applying the operation in-place, instead of collecting all insertions
   into a buffer first. This is safe as the `_perViewChange` operation
   that used to be done on each `RecordViewTuple` is entirely local to
   the tuple itself. Hence, it is invariant to execution ordering which
   means that the `_perViewChange` can be executed directly during the
   `forEachOperation` loop.

PR Close angular#44315
@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 Jan 2, 2022
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: common Issues related to APIs in the @angular/common package area: performance cla: yes target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants