diff --git a/packages/core/test/render3/perf/BUILD.bazel b/packages/core/test/render3/perf/BUILD.bazel index f1082aad85634..5ce9e99b3a1f8 100644 --- a/packages/core/test/render3/perf/BUILD.bazel +++ b/packages/core/test/render3/perf/BUILD.bazel @@ -13,6 +13,14 @@ ts_library( ], ) +ng_rollup_bundle( + name = "class_binding", + entry_point = ":class_binding/index.ts", + deps = [ + ":perf_lib", + ], +) + ng_rollup_bundle( name = "directive_instantiate", entry_point = ":directive_instantiate/index.ts", diff --git a/packages/core/test/render3/perf/class_binding/index.ts b/packages/core/test/render3/perf/class_binding/index.ts new file mode 100644 index 0000000000000..c2293deb85d5f --- /dev/null +++ b/packages/core/test/render3/perf/class_binding/index.ts @@ -0,0 +1,165 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {ɵɵproperty} from '@angular/core/src/core'; +import {AttributeMarker, TAttributes} from '@angular/core/src/render3/interfaces/node'; +import {ɵɵelement} from '../../../../src/render3/instructions/element'; +import {ɵɵclassMap, ɵɵclassProp} from '../../../../src/render3/instructions/styling'; +import {ComponentTemplate, RenderFlags} from '../../../../src/render3/interfaces/definition'; +import {createBenchmark} from '../micro_bench'; +import {setupTestHarness} from '../setup'; + + +const CLASSES_1_A = 'one'; +const CLASSES_1_B = CLASSES_1_A.toUpperCase(); +const CLASSES_2_A = 'one two'; +const CLASSES_2_B = CLASSES_2_A.toUpperCase(); +const CLASSES_10_A = 'one two three four five six seven eight nine ten'; +const CLASSES_10_B = CLASSES_10_A.toUpperCase(); +let toggleClasses = true; + +const consts: TAttributes[] = [ + [AttributeMarker.Classes, 'A', 'B'] // 0 +]; +const context: any = {}; +const createClassBindingBenchmark = createBenchmark('class binding: create:'); +const updateClassBindingBenchmark = createBenchmark('class binding: update:'); +const noopClassBindingBenchmark = createBenchmark('class binding: noop:'); +function benchmark(name: string, template: ComponentTemplate) { + const harness = setupTestHarness(template, 1, 1, 1000, context, consts); + + const createProfile = createClassBindingBenchmark(name); + console.profile('create: ' + name); + while (createProfile()) { + harness.createEmbeddedLView(); + } + console.profileEnd(); + + + const updateProfile = updateClassBindingBenchmark(name); + console.profile('update: ' + name); + while (updateProfile()) { + toggleClasses = !toggleClasses; + harness.detectChanges(); + } + console.profileEnd(); + + const noopProfile = noopClassBindingBenchmark(name); + console.profile('nop: ' + name); + while (noopProfile()) { + harness.detectChanges(); + } + console.profileEnd(); +} + +`
`; +benchmark(`[class]="CLASSES_1"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div'); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_1_A : CLASSES_1_B); + } +}); + + +`
`; +benchmark(`[class]="CLASSES_2"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div'); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_2_A : CLASSES_2_B); + } +}); + + +`
`; +benchmark(`[class]="CLASSES_10"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div'); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_10_A : CLASSES_10_B); + } +}); + + +`
`; +benchmark(`class="A B"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div', 0); + } + if (rf & 2) { + } +}); + + +`
`; +benchmark(`class="A B" [class]="CLASSES_1"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div', 0); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_1_A : CLASSES_1_B); + } +}); + + +`
`; +benchmark(`class="A B" [class]="CLASSES_10"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div', 0); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_10_A : CLASSES_10_B); + } +}); + +`
`; +benchmark(`class="A B" [class]="CLASSES_1" [class.foo]="exp"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div', 0); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_1_A : CLASSES_1_B); + ɵɵclassProp('foo', toggleClasses); + } +}); + +`
`; +benchmark( + `class="A B" [class]="CLASSES_10" [class.foo]="exp"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div', 0); + } + if (rf & 2) { + ɵɵclassMap(toggleClasses ? CLASSES_10_A : CLASSES_10_B); + ɵɵclassProp('foo', toggleClasses); + } + }); + + +`
`; +benchmark(`[className]="CLASSES_10"`, function(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelement(0, 'div'); + } + if (rf & 2) { + ɵɵproperty('className', toggleClasses ? CLASSES_10_A : CLASSES_10_B); + } +}); + +createClassBindingBenchmark.report(); +updateClassBindingBenchmark.report(); +noopClassBindingBenchmark.report(); \ No newline at end of file diff --git a/packages/core/test/render3/perf/setup.ts b/packages/core/test/render3/perf/setup.ts index a449dc97adcd7..b9eac8431df3c 100644 --- a/packages/core/test/render3/perf/setup.ts +++ b/packages/core/test/render3/perf/setup.ts @@ -5,11 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {addToViewTree, createLContainer, createLView, createTNode, createTView, getOrCreateTNode, renderView} from '../../../src/render3/instructions/shared'; +import {addToViewTree, createLContainer, createLView, createTNode, createTView, getOrCreateTNode, refreshView, renderView} from '../../../src/render3/instructions/shared'; import {ComponentTemplate} from '../../../src/render3/interfaces/definition'; import {TAttributes, TNodeType, TViewNode} from '../../../src/render3/interfaces/node'; import {RComment} from '../../../src/render3/interfaces/renderer'; -import {LView, LViewFlags, TView} from '../../../src/render3/interfaces/view'; +import {LView, LViewFlags, RENDERER, RENDERER_FACTORY, TView} from '../../../src/render3/interfaces/view'; import {insertView} from '../../../src/render3/node_manipulation'; import {NoopRenderer, NoopRendererFactory, WebWorkerRenderNode} from './noop_renderer'; @@ -25,33 +25,63 @@ export function createAndRenderLView( export function setupRootViewWithEmbeddedViews( templateFn: ComponentTemplate| null, decls: number, vars: number, noOfViews: number, embeddedViewContext: any = {}, consts: TAttributes[] | null = null): LView { + return setupTestHarness(templateFn, decls, vars, noOfViews, embeddedViewContext, consts) + .hostLView; +} + +export interface TestHarness { + hostLView: LView; + hostTView: TView; + embeddedTView: TView; + createEmbeddedLView(): LView; + detectChanges(): void; +} + +export function setupTestHarness( + templateFn: ComponentTemplate| null, decls: number, vars: number, noOfViews: number, + embeddedViewContext: any = {}, consts: TAttributes[] | null = null): TestHarness { // Create a root view with a container - const rootTView = createTView(-1, null, 1, 0, null, null, null, null, consts); - const tContainerNode = getOrCreateTNode(rootTView, null, 0, TNodeType.Container, null, null); - const rootLView = createLView( - null, rootTView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null, + const hostTView = createTView(-1, null, 1, 0, null, null, null, null, consts); + const tContainerNode = getOrCreateTNode(hostTView, null, 0, TNodeType.Container, null, null); + const hostLView = createLView( + null, hostTView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null, new NoopRendererFactory(), new NoopRenderer()); const mockRNode = new WebWorkerRenderNode(); const lContainer = createLContainer( - mockRNode as RComment, rootLView, mockRNode as RComment, tContainerNode, true); - addToViewTree(rootLView, lContainer); + mockRNode as RComment, hostLView, mockRNode as RComment, tContainerNode, true); + addToViewTree(hostLView, lContainer); + // run in the host view in creation mode to initialize TNode structures (first template pass) + renderView(hostLView, hostTView, null); // create test embedded views - const embeddedTView = createTView(-1, templateFn, decls, vars, null, null, null, null, null); - const viewTNode = createTNode(rootTView, null, TNodeType.View, -1, null, null) as TViewNode; + const embeddedTView = createTView(-1, templateFn, decls, vars, null, null, null, null, consts); + const viewTNode = createTNode(hostTView, null, TNodeType.View, -1, null, null) as TViewNode; + const rendererFactory = hostLView[RENDERER_FACTORY]; + const renderer = hostLView[RENDERER]; - // create embedded views and add them to the container - for (let i = 0; i < noOfViews; i++) { + function createEmbeddedLView(): LView { const embeddedLView = createLView( - rootLView, embeddedTView, embeddedViewContext, LViewFlags.CheckAlways, null, viewTNode, - new NoopRendererFactory(), new NoopRenderer()); - renderView(embeddedLView, embeddedTView, null); - insertView(embeddedLView, lContainer, i); + hostLView, embeddedTView, embeddedViewContext, LViewFlags.CheckAlways, null, viewTNode, + rendererFactory, renderer); + renderView(embeddedLView, embeddedTView, embeddedViewContext); + return embeddedLView; + } + + function detectChanges(): void { + refreshView(hostLView, hostTView, hostTView.template, embeddedViewContext); } - // run in the creation mode to set flags etc. - renderView(rootLView, rootTView, null); + // create embedded views and add them to the container + for (let i = 0; i < noOfViews; i++) { + insertView(createEmbeddedLView(), lContainer, i); + } - return rootLView; + return { + hostLView: hostLView, + hostTView: hostTView, + embeddedTView: embeddedTView, + createEmbeddedLView: createEmbeddedLView, + detectChanges: detectChanges, + }; }