perf(core): avoid storing LView in __ngContext__ (#41358)

Currently we save a reference to an `LView` on most DOM nodes created by Angular either by saving
the `LView` directly in the `__ngContext__` or by saving the `LContext` which has a reference to
the `LView`. This can be a problem if the DOM node is retained in memory, because the `LView` has
references to all of the child nodes of the view, as well as other internal data structures.

Previously we tried to resolve the issue by clearing the `__ngContext__` when a node is removed
(see https://github.com/angular/angular/pull/36011), but we decided not to proceeed, because it can
slow down destruction due to a megamorphic write.

These changes aim to address the issue while reducing the performance impact by assigning a unique
ID when an `LView` is created and adding it to `__ngContext__`. All active views are tracked in
a map where their unique ID is used as the key. We don't need to worry about leaks within that map,
because `LView`s are an internal data structure and we have complete control over when they are
created and destroyed.

Fixes #41047.

PR Close #41358
This commit is contained in:
Kristiyan Kostadinov
2021-04-26 09:31:41 -07:00
committed by Jessica Janiuk
parent 6d9e3400e1
commit 18b33e79d3
37 changed files with 650 additions and 396 deletions
+17 -13
View File
@@ -262,9 +262,10 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
}
get name(): string {
const context = getLContext(this.nativeNode);
if (context !== null) {
const lView = context.lView;
const context = getLContext(this.nativeNode)!;
const lView = context ? context.lView : null;
if (lView !== null) {
const tData = lView[TVIEW].data;
const tNode = tData[context.nodeIndex] as TNode;
return tNode.value!;
@@ -286,12 +287,13 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
* - attribute bindings (e.g. `[attr.role]="menu"`)
*/
get properties(): {[key: string]: any;} {
const context = getLContext(this.nativeNode);
if (context === null) {
const context = getLContext(this.nativeNode)!;
const lView = context ? context.lView : null;
if (lView === null) {
return {};
}
const lView = context.lView;
const tData = lView[TVIEW].data;
const tNode = tData[context.nodeIndex] as TNode;
@@ -312,12 +314,13 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
return attributes;
}
const context = getLContext(element);
if (context === null) {
const context = getLContext(element)!;
const lView = context ? context.lView : null;
if (lView === null) {
return {};
}
const lView = context.lView;
const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs;
const lowercaseTNodeAttrs: string[] = [];
@@ -502,11 +505,12 @@ function _queryAllR3(
function _queryAllR3(
parentElement: DebugElement, predicate: Predicate<DebugElement>|Predicate<DebugNode>,
matches: DebugElement[]|DebugNode[], elementsOnly: boolean) {
const context = getLContext(parentElement.nativeNode);
if (context !== null) {
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
const context = getLContext(parentElement.nativeNode)!;
const lView = context ? context.lView : null;
if (lView !== null) {
const parentTNode = lView[TVIEW].data[context.nodeIndex] as TNode;
_queryNodeChildrenR3(
parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode);
parentTNode, lView, predicate, matches, elementsOnly, parentElement.nativeNode);
} else {
// If the context is null, then `parentElement` was either created with Renderer2 or native DOM
// APIs.