diff --git a/packages/core/test/render3/perf/BUILD.bazel b/packages/core/test/render3/perf/BUILD.bazel index 8b374c83a3..c2f55bb79d 100644 --- a/packages/core/test/render3/perf/BUILD.bazel +++ b/packages/core/test/render3/perf/BUILD.bazel @@ -176,3 +176,16 @@ ng_benchmark( name = "map_based_style_and_class_bindings", bundle = ":map_based_style_and_class_bindings_lib", ) + +ng_rollup_bundle( + name = "duplicate_style_and_class_bindings_lib", + entry_point = ":duplicate_style_and_class_bindings/index.ts", + deps = [ + ":perf_lib", + ], +) + +ng_benchmark( + name = "duplicate_style_and_class_bindings", + bundle = ":duplicate_style_and_class_bindings_lib", +) diff --git a/packages/core/test/render3/perf/duplicate_style_and_class_bindings/index.ts b/packages/core/test/render3/perf/duplicate_style_and_class_bindings/index.ts new file mode 100644 index 0000000000..4ac9aea0f2 --- /dev/null +++ b/packages/core/test/render3/perf/duplicate_style_and_class_bindings/index.ts @@ -0,0 +1,175 @@ +/** + * @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 {ɵɵadvance} from '../../../../src/render3/instructions/advance'; +import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element'; +import {refreshView} from '../../../../src/render3/instructions/shared'; +import {ɵɵclassProp, ɵɵstyleProp} from '../../../../src/render3/instructions/styling'; +import {RenderFlags} from '../../../../src/render3/interfaces/definition'; +import {TVIEW} from '../../../../src/render3/interfaces/view'; +import {createBenchmark} from '../micro_bench'; +import {setupRootViewWithEmbeddedViews} from '../setup'; +import {defineBenchmarkTestDirective} from '../shared'; + +` +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
`; +function testTemplate(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelementStart(0, 'section'); + ɵɵelement(1, 'div', 0); + ɵɵelement(2, 'div', 0); + ɵɵelement(3, 'div', 0); + ɵɵelement(4, 'div', 0); + ɵɵelement(5, 'div', 0); + ɵɵelement(6, 'div', 0); + ɵɵelement(7, 'div', 0); + ɵɵelement(8, 'div', 0); + ɵɵelement(9, 'div', 0); + ɵɵelement(10, 'div', 0); + ɵɵelementEnd(); + } + if (rf & 2) { + // 1 + ɵɵadvance(1); + ɵɵstyleProp('width', '100px'); + ɵɵclassProp('foo', true); + + // 2 + ɵɵadvance(1); + ɵɵstyleProp('width', '200px'); + ɵɵclassProp('foo', true); + + // 3 + ɵɵadvance(1); + ɵɵstyleProp('width', '300px'); + ɵɵclassProp('foo', true); + + // 4 + ɵɵadvance(1); + ɵɵstyleProp('width', '400px'); + ɵɵclassProp('foo', true); + + // 5 + ɵɵadvance(1); + ɵɵstyleProp('width', '500px'); + ɵɵclassProp('foo', true); + + // 6 + ɵɵadvance(1); + ɵɵstyleProp('width', '600px'); + ɵɵclassProp('foo', true); + + // 7 + ɵɵadvance(1); + ɵɵstyleProp('width', '700px'); + ɵɵclassProp('foo', true); + + // 8 + ɵɵadvance(1); + ɵɵstyleProp('width', '800px'); + ɵɵclassProp('foo', true); + + // 9 + ɵɵadvance(1); + ɵɵstyleProp('width', '900px'); + ɵɵclassProp('foo', true); + + // 10 + ɵɵadvance(1); + ɵɵstyleProp('width', '1000px'); + ɵɵclassProp('foo', true); + } +} + +function dirThatSetsWidthHostBindings(rf: RenderFlags, ctx: any) { + if (rf & 2) { + ɵɵstyleProp('width', '999px'); + } +} + +function dirThatSetsFooClassHostBindings(rf: RenderFlags, ctx: any) { + if (rf & 2) { + ɵɵclassProp('foo', false); + } +} + +const rootLView = setupRootViewWithEmbeddedViews( + testTemplate, 11, 10, 1000, null, + [ + ['dir-that-sets-width', '', 'dir-that-sets-foo-class', ''], + ], + [ + defineBenchmarkTestDirective('dir-that-sets-width', dirThatSetsWidthHostBindings), + defineBenchmarkTestDirective('dir-that-sets-foo-class', dirThatSetsFooClassHostBindings), + ]); +const rootTView = rootLView[TVIEW]; + +// scenario to benchmark +const duplicateStyleAndClassBindingsBenchmark = + createBenchmark('duplicate style and class bindings'); +const refreshTime = duplicateStyleAndClassBindingsBenchmark('refresh'); + +// run change detection in the update mode +console.profile('duplicate_style_and_class_bindings_refresh'); +while (refreshTime()) { + refreshView(rootLView, rootTView, null, null); +} +console.profileEnd(); + +// report results +duplicateStyleAndClassBindingsBenchmark.report(); diff --git a/packages/core/test/render3/perf/setup.ts b/packages/core/test/render3/perf/setup.ts index 530008987e..67ea569d7b 100644 --- a/packages/core/test/render3/perf/setup.ts +++ b/packages/core/test/render3/perf/setup.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {addToViewTree, createLContainer, createLView, createTNode, createTView, getOrCreateTNode, refreshView, renderView} from '../../../src/render3/instructions/shared'; -import {ComponentTemplate} from '../../../src/render3/interfaces/definition'; +import {ComponentTemplate, DirectiveDefList} from '../../../src/render3/interfaces/definition'; import {TAttributes, TNodeType, TViewNode} from '../../../src/render3/interfaces/node'; import {RendererFactory3, domRendererFactory3} from '../../../src/render3/interfaces/renderer'; import {LView, LViewFlags, TView, TViewType} from '../../../src/render3/interfaces/view'; @@ -28,8 +28,10 @@ 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) + embeddedViewContext: any = {}, consts: TAttributes[] | null = null, + directiveRegistry: DirectiveDefList | null = null): LView { + return setupTestHarness( + templateFn, decls, vars, noOfViews, embeddedViewContext, consts, directiveRegistry) .hostLView; } @@ -43,7 +45,8 @@ export interface TestHarness { export function setupTestHarness( templateFn: ComponentTemplate| null, decls: number, vars: number, noOfViews: number, - embeddedViewContext: any = {}, consts: TAttributes[] | null = null): TestHarness { + embeddedViewContext: any = {}, consts: TAttributes[] | null = null, + directiveRegistry: DirectiveDefList | null = null): TestHarness { // Create a root view with a container const hostTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, consts); const tContainerNode = getOrCreateTNode(hostTView, null, 0, TNodeType.Container, null, null); @@ -58,8 +61,8 @@ export function setupTestHarness( // create test embedded views - const embeddedTView = - createTView(TViewType.Embedded, -1, templateFn, decls, vars, null, null, null, null, consts); + const embeddedTView = createTView( + TViewType.Embedded, -1, templateFn, decls, vars, directiveRegistry, null, null, null, consts); const viewTNode = createTNode(hostTView, null, TNodeType.View, -1, null, null) as TViewNode; function createEmbeddedLView(): LView { diff --git a/packages/core/test/render3/perf/shared.ts b/packages/core/test/render3/perf/shared.ts new file mode 100644 index 0000000000..353a03046e --- /dev/null +++ b/packages/core/test/render3/perf/shared.ts @@ -0,0 +1,23 @@ +/** + * @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 {ɵɵdefineDirective} from '@angular/core/src/core'; + +import {HostBindingsFunction} from '../../../src/render3/interfaces/definition'; + +export function defineBenchmarkTestDirective( + selector: string, hostBindings: HostBindingsFunction, type?: any) { + return ɵɵdefineDirective({ + hostBindings, + type: type || FakeDirectiveType, + selectors: [['', selector, '']], + }); +} + +class FakeDirectiveType { + static ɵfac = () => { return {}; }; +}