perf(ivy): limit allocation of LQueries_ objects (#30664)

Before this change we would systematically call LQueries.clone() when executting
elementStart / elementContainerStart instructions. This was often unnecessary as
LQueries can be mutated under 2 conditions only:
- we are crossing an element that has directives with content queries
  (new queries must be added);
- we are descending into element hierarchy (creating a child element of an existing element)
  and the current LQueries object is tracking shallow queries (shallow queries are removed).

With this PR LQueires.clone() is only done when needed and this gratelly reduces number
of LQueries object created: in the "expanding rows" benchmark number of allocated
(and often GCed just after!) LQueries is reduced from ~100k -> ~35k. This represents
over 1MB of memory that is not allocated.

PR Close #30664
This commit is contained in:
Pawel Kozlowski 2019-05-24 17:47:21 +02:00 committed by Misko Hevery
parent 07cd65b5ec
commit 8154433130
4 changed files with 50 additions and 20 deletions

View File

@ -125,7 +125,7 @@ export function ɵɵelementStart(
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone();
lView[QUERIES] = currentQueries.clone(tNode);
}
executeContentQueries(tView, tNode, lView);
}
@ -153,7 +153,8 @@ export function ɵɵelementEnd(): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Element);
const lView = getLView();
const currentQueries = lView[QUERIES];
if (currentQueries) {
// Go back up to parent queries only if queries have been cloned on this element.
if (currentQueries && previousOrParentTNode.index === currentQueries.nodeIndex) {
lView[QUERIES] = currentQueries.parent;
}

View File

@ -65,7 +65,7 @@ export function ɵɵelementContainerStart(
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone();
lView[QUERIES] = currentQueries.clone(tNode);
}
executeContentQueries(tView, tNode, lView);
}
@ -89,7 +89,8 @@ export function ɵɵelementContainerEnd(): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
const currentQueries = lView[QUERIES];
if (currentQueries) {
// Go back up to parent queries only if queries have been cloned on this element.
if (currentQueries && previousOrParentTNode.index === currentQueries.nodeIndex) {
lView[QUERIES] = currentQueries.parent;
}

View File

@ -25,11 +25,32 @@ export interface LQueries {
parent: LQueries|null;
/**
* Ask queries to prepare copy of itself. This assures that tracking new queries on content nodes
* doesn't mutate list of queries tracked on a parent node. We will clone LQueries before
* constructing content queries.
* The index of the node on which this LQueries instance was created / cloned in a given LView.
*
* This index is stored to minimize LQueries cloning: we can observe that LQueries can be mutated
* only under 2 conditions:
* - we are crossing an element that has directives with content queries (new queries are added);
* - we are descending into element hierarchy (creating a child element of an existing element)
* and the current LQueries object is tracking shallow queries (shallow queries are removed).
*
* Since LQueries are not cloned systematically we need to know exactly where (on each element)
* cloning occurred, so we can properly restore the set of tracked queries when going up the
* elements hierarchy.
*
* Always set to -1 for view queries as view queries are created before we process any node in a
* given view.
*/
clone(): LQueries;
nodeIndex: number;
/**
* Ask queries to prepare a copy of itself. This ensures that:
* - tracking new queries on content nodes doesn't mutate list of queries tracked on a parent
* node;
* - we don't track shallow queries when descending into elements hierarchy.
*
* We will clone LQueries before constructing content queries
*/
clone(tNode: TNode): LQueries;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results

View File

@ -25,8 +25,8 @@ import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW, TView} from './interfaces/view';
import {getCurrentQueryIndex, getIsParent, getLView, isCreationMode, setCurrentQueryIndex} from './state';
import {loadInternal} from './util/view_utils';
import {getCurrentQueryIndex, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
import {isContentQueryHost, loadInternal} from './util/view_utils';
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
@ -92,7 +92,7 @@ class LQuery<T> {
export class LQueries_ implements LQueries {
constructor(
public parent: LQueries_|null, private shallow: LQuery<any>|null,
private deep: LQuery<any>|null) {}
private deep: LQuery<any>|null, public nodeIndex: number = -1) {}
track<T>(queryList: QueryList<T>, predicate: Type<T>|string[], descend?: boolean, read?: Type<T>):
void {
@ -103,7 +103,11 @@ export class LQueries_ implements LQueries {
}
}
clone(): LQueries { return new LQueries_(this, null, this.deep); }
clone(tNode: TNode): LQueries {
return this.shallow !== null || isContentQueryHost(tNode) ?
new LQueries_(this, null, this.deep, tNode.index) :
this;
}
container(): LQueries|null {
const shallowResults = copyQueriesToContainer(this.shallow);
@ -352,11 +356,11 @@ type QueryList_<T> = QueryList<T>& {_valuesTree: any[], _static: boolean};
*/
function createQueryListInLView<T>(
// TODO: "read" should be an AbstractType (FW-486)
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any,
isStatic: boolean): QueryList<T> {
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any, isStatic: boolean,
nodeIndex: number): QueryList<T> {
ngDevMode && assertPreviousIsParent(getIsParent());
const queryList = new QueryList<T>() as QueryList_<T>;
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null));
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null, nodeIndex));
queryList._valuesTree = [];
queryList._static = isStatic;
queries.track(queryList, predicate, descend, read);
@ -430,7 +434,7 @@ function viewQueryInternal<T>(
}
const index = getCurrentQueryIndex();
const queryList: QueryList<T> =
createQueryListInLView<T>(lView, predicate, descend, read, isStatic);
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, -1);
store(index - HEADER_OFFSET, queryList);
setCurrentQueryIndex(index + 1);
return queryList;
@ -465,16 +469,18 @@ export function ɵɵcontentQuery<T>(
read: any): QueryList<T> {
const lView = getLView();
const tView = lView[TVIEW];
return contentQueryInternal(lView, tView, directiveIndex, predicate, descend, read, false);
const tNode = getPreviousOrParentTNode();
return contentQueryInternal(
lView, tView, directiveIndex, predicate, descend, read, false, tNode.index);
}
function contentQueryInternal<T>(
lView: LView, tView: TView, directiveIndex: number, predicate: Type<any>| string[],
descend: boolean,
// TODO(FW-486): "read" should be an AbstractType
read: any, isStatic: boolean): QueryList<T> {
read: any, isStatic: boolean, nodeIndex: number): QueryList<T> {
const contentQuery: QueryList<T> =
createQueryListInLView<T>(lView, predicate, descend, read, isStatic);
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, nodeIndex);
(lView[CONTENT_QUERIES] || (lView[CONTENT_QUERIES] = [])).push(contentQuery);
if (tView.firstTemplatePass) {
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
@ -505,7 +511,8 @@ export function ɵɵstaticContentQuery<T>(
read: any): void {
const lView = getLView();
const tView = lView[TVIEW];
contentQueryInternal(lView, tView, directiveIndex, predicate, descend, read, true);
const tNode = getPreviousOrParentTNode();
contentQueryInternal(lView, tView, directiveIndex, predicate, descend, read, true, tNode.index);
tView.staticContentQueries = true;
}