Skip to content

Commit

Permalink
perf(ivy): Improve performance of transplanted views
Browse files Browse the repository at this point in the history
  • Loading branch information
mhevery committed Nov 13, 2019
1 parent 9485e16 commit 21ed744
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 94 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/render3/instructions/container.ts
Expand Up @@ -9,7 +9,7 @@ import {assertDataInRange, assertEqual} from '../../util/assert';
import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPostOrderHooks} from '../hooks';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {ComponentTemplate} from '../interfaces/definition';
import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType, TViewNode} from '../interfaces/node';
import {isDirectiveHost} from '../interfaces/type_checks';
Expand Down Expand Up @@ -160,7 +160,7 @@ export function ɵɵcontainerRefreshEnd(): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);

const lContainer: LContainer = getLView()[previousOrParentTNode.index];
const nextIndex = lContainer[ACTIVE_INDEX];
const nextIndex = lContainer[ACTIVE_INDEX] >> ActiveIndexFlag.SHIFT;

// remove extra views at the end of the container
while (nextIndex < lContainer.length - CONTAINER_HEADER_OFFSET) {
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/render3/instructions/embedded_view.ts
Expand Up @@ -8,7 +8,7 @@

import {assertDefined, assertEqual} from '../../util/assert';
import {assertLContainerOrUndefined} from '../assert';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {RenderFlags} from '../interfaces/definition';
import {TContainerNode, TNodeType} from '../interfaces/node';
import {CONTEXT, LView, LViewFlags, PARENT, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
Expand Down Expand Up @@ -38,7 +38,8 @@ export function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars:
const lContainer = lView[containerTNode.index] as LContainer;

ngDevMode && assertNodeType(containerTNode, TNodeType.Container);
let viewToRender = scanForView(lContainer, lContainer[ACTIVE_INDEX] !, viewBlockId);
let viewToRender =
scanForView(lContainer, lContainer[ACTIVE_INDEX] ! >> ActiveIndexFlag.SHIFT, viewBlockId);

if (viewToRender) {
setIsParent();
Expand All @@ -57,9 +58,9 @@ export function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars:
if (lContainer) {
if (isCreationMode(viewToRender)) {
// it is a new view, insert it into collection of views for a given container
insertView(viewToRender, lContainer, lContainer[ACTIVE_INDEX] !);
insertView(viewToRender, lContainer, lContainer[ACTIVE_INDEX] ! >> ActiveIndexFlag.SHIFT);
}
lContainer[ACTIVE_INDEX] !++;
lContainer[ACTIVE_INDEX] += ActiveIndexFlag.INCREMENT;
}
return isCreationMode(viewToRender) ? RenderFlags.Create | RenderFlags.Update :
RenderFlags.Update;
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/render3/instructions/lview_debug.ts
Expand Up @@ -7,11 +7,11 @@
*/

import {AttributeMarker, ComponentTemplate} from '..';
import {SchemaMetadata, Type} from '../../core';
import {SchemaMetadata} from '../../core';
import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
Expand Down Expand Up @@ -433,7 +433,11 @@ export function buildDebugNode(tNode: TNode, lView: LView, nodeIndex: number): D
export class LContainerDebug {
constructor(private readonly _raw_lContainer: LContainer) {}

get activeIndex(): number { return this._raw_lContainer[ACTIVE_INDEX]; }
get activeIndex(): number { return this._raw_lContainer[ACTIVE_INDEX] >> ActiveIndexFlag.SHIFT; }
get hasTransplantedViews(): boolean {
return (this._raw_lContainer[ACTIVE_INDEX] & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) ===
ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS;
}
get views(): LViewDebug[] {
return this._raw_lContainer.slice(CONTAINER_HEADER_OFFSET)
.map(toDebug as(l: LView) => LViewDebug);
Expand Down
102 changes: 64 additions & 38 deletions packages/core/src/render3/instructions/shared.ts
Expand Up @@ -10,24 +10,24 @@ import {ErrorHandler} from '../../error_handler';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/sanitizer';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame, assertSame} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {assertFirstCreatePass, assertLView} from '../assert';
import {assertFirstCreatePass, assertLContainer, assertLView} from '../assert';
import {attachPatchData} from '../context_discovery';
import {getFactoryDef} from '../definition';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
import {throwMultipleComponentError} from '../errors';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from '../interfaces/container';
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from '../interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
import {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {ActiveElementFlags, enterView, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
Expand Down Expand Up @@ -172,6 +172,9 @@ export function createLView<T>(
lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null !;
lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null;
lView[T_HOST] = tHostNode;
lView[DECLARATION_COMPONENT_VIEW] = tView.type == TViewType.Embedded ?
(parentLView === null ? null : parentLView ![DECLARATION_COMPONENT_VIEW]) :
lView;
ngDevMode && attachLViewDebug(lView);
return lView;
}
Expand Down Expand Up @@ -636,6 +639,7 @@ export function createTView(
schemas, // schemas: SchemaMetadata[]|null,
consts) : // consts: TConstants|null
{
type: type,
id: viewIndex,
blueprint: blueprint,
template: templateFn,
Expand Down Expand Up @@ -1450,15 +1454,15 @@ export function createLContainer(
ngDevMode && !isProceduralRenderer(currentView[RENDERER]) && assertDomNode(native);
// https://jsperf.com/array-literal-vs-new-array-really
const lContainer: LContainer = new (ngDevMode ? LContainerArray : Array)(
hostNative, // host native
true, // Boolean `true` in this position signifies that this is an `LContainer`
-1, // active index
currentView, // parent
null, // next
null, // queries
tNode, // t_host
native, // native,
null, // view refs
hostNative, // host native
true, // Boolean `true` in this position signifies that this is an `LContainer`
ActiveIndexFlag.NO_INLINE_EMBEDDED_VIEWS << ActiveIndexFlag.SHIFT, // active index
currentView, // parent
null, // next
null, // queries
tNode, // t_host
native, // native,
null, // view refs
);
ngDevMode && attachLContainerDebug(lContainer);
return lContainer;
Expand All @@ -1474,45 +1478,67 @@ function refreshDynamicEmbeddedViews(lView: LView) {
while (viewOrContainer !== null) {
// Note: viewOrContainer can be an LView or an LContainer instance, but here we are only
// interested in LContainer
if (isLContainer(viewOrContainer) && viewOrContainer[ACTIVE_INDEX] === -1) {
let activeIndexFlag: ActiveIndexFlag;
if (isLContainer(viewOrContainer) &&
(activeIndexFlag = viewOrContainer[ACTIVE_INDEX]) >> ActiveIndexFlag.SHIFT ===
ActiveIndexFlag.NO_INLINE_EMBEDDED_VIEWS) {
for (let i = CONTAINER_HEADER_OFFSET; i < viewOrContainer.length; i++) {
const embeddedLView = viewOrContainer[i];
const embeddedTView = embeddedLView[TVIEW];
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
refreshView(embeddedLView, embeddedTView, embeddedTView.template, embeddedLView[CONTEXT] !);
}
const movedViews = viewOrContainer[MOVED_VIEWS];
if (movedViews !== null) {
if ((activeIndexFlag & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) !== 0) {
// We should only CD moved views if the component where they were inserted does not match
// the component where they were declared. Moved views also contains intra component moves,
// which we don't care about.
// TODO(misko): this is not the most efficient way to do this as we have to do a lot of
// searches. Will refactor for performance later.
const declaredComponentLView = findComponentView(lView);
for (let i = 0; i < movedViews.length; i++) {
const movedLView = movedViews[i] !;
let parentLView = movedLView[PARENT];
while (isLContainer(parentLView)) {
parentLView = parentLView[PARENT];
}
const insertedComponentLView = findComponentView(parentLView !);
const insertionIsOnPush =
(insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) !== LViewFlags.CheckAlways;
if (insertionIsOnPush && insertedComponentLView !== declaredComponentLView) {
// Here we know that the template has been transplanted across components
// (not just moved within a component)
const movedTView = movedLView[TVIEW];
ngDevMode && assertDefined(movedTView, 'TView must be allocated');
refreshView(movedLView, movedTView, movedTView.template, movedLView[CONTEXT] !);
}
}
// the component where they were declared and insertion is on-push. Moved views also
// contains intra component moves, or check-always which need to be skipped.
refreshTransplantedViews(viewOrContainer, lView[DECLARATION_COMPONENT_VIEW] !);
}
}
viewOrContainer = viewOrContainer[NEXT];
}
}


/**
* Refresh transplanted LViews.
*
* See: `ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS` and `LView[DECLARATION_COMPONENT_VIEW]` for
* explanation of transplanted views.
*
* @param lContainer The `LContainer` which has transplanted views.
* @param declaredComponentLView The `lContainer` parent component `LView`.
*/
function refreshTransplantedViews(lContainer: LContainer, declaredComponentLView: LView) {
const movedViews = lContainer[MOVED_VIEWS] !;
ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS');
for (let i = 0; i < movedViews.length; i++) {
const movedLView = movedViews[i] !;
const insertionLContainer = movedLView[PARENT] as LContainer;
ngDevMode && assertLContainer(insertionLContainer);
const insertedComponentLView = insertionLContainer[PARENT][DECLARATION_COMPONENT_VIEW] !;
ngDevMode && assertDefined(insertedComponentLView, 'Missing LView');
// Check if we have a transplanted view by compering declaration and insertion location.
if (insertedComponentLView !== declaredComponentLView) {
// Yes the `LView` is transplanted.
// Here we would like to know if the component is `OnPush`. We don't have
// explicit `OnPush` flag instead we set `CheckAlways` to false (which is `OnPush`)
// Not to be confused with `ManualOnPush` which is used with wether a DOM event
// should automatically mark a view as dirty.
const insertionComponentIsOnPush =
(insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) === 0;
if (insertionComponentIsOnPush) {
// Here we know that the template has been transplanted across components and is
// on-push (not just moved within a component). If the insertion is marked dirty, than
// there is no need to CD here as we will do it again later when we get to insertion
// point.
const movedTView = movedLView[TVIEW];
ngDevMode && assertDefined(movedTView, 'TView must be allocated');
refreshView(movedLView, movedTView, movedTView.template, movedLView[CONTEXT] !);
}
}
}
}

/////////////

Expand Down
44 changes: 43 additions & 1 deletion packages/core/src/render3/interfaces/container.ts
Expand Up @@ -46,6 +46,41 @@ export const VIEW_REFS = 8;
*/
export const CONTAINER_HEADER_OFFSET = 9;


/**
* Used to track:
* - Inline embedded views (see: `ɵɵembeddedViewStart`)
* - Transplanted `LView`s (see: `LView[DECLARATION_COMPONENT_VIEW])`
*/
export const enum ActiveIndexFlag {
/**
* Flag which signifies that the `LContainer` does not have any inline embedded views.
*/
NO_INLINE_EMBEDDED_VIEWS = -1,

/**
* Flag to signify that this `LContainer` may have transplanted views which need to be CD.
* (see: `LView[DECLARATION_COMPONENT_VIEW])`.
*
* This flag once set is never unset for the `LContainer`. This means that when unset we can skip
* a lot of work in `refreshDynamicEmbeddedViews`. But when set we still need to verify
* that the `MOVED_VIEWS` are transplanted and on-push.
*/
HAS_TRANSPLANTED_VIEWS = 1,

/**
* Number of bits to shift inline embedded views counter to make space for other flags.
*/
SHIFT = 1,


/**
* When incrementing inline embedded views, the amount to increment to leave space for other
* flags.
*/
INCREMENT = 1 << SHIFT,
}

/**
* The state associated with a container.
*
Expand Down Expand Up @@ -75,8 +110,15 @@ export interface LContainer extends Array<any> {
* In the case the LContainer is created for a ViewContainerRef,
* it is set to null to identify this scenario, as indices are "absolute" in that case,
* i.e. provided directly by the user of the ViewContainerRef API.
*
* This is used by `ɵɵembeddedViewStart` to track which `LView` is currently active.
* Because `ɵɵembeddedViewStart` is not generated by the compiler this feature is essentially
* unused.
*
* The lowest bit will signals that this `LContainer` has transplanted views which need to be CD
* as part of the declaration CD. (See `LView[DECLARATION_COMPONENT_VIEW]`)
*/
[ACTIVE_INDEX]: number;
[ACTIVE_INDEX]: ActiveIndexFlag;

/**
* Access to the parent view is necessary so we can propagate back
Expand Down

0 comments on commit 21ed744

Please sign in to comment.