diff --git a/modules/benchmarks/src/change_detection/BUILD.bazel b/modules/benchmarks/src/change_detection/BUILD.bazel new file mode 100644 index 0000000000..ccf0bdbe59 --- /dev/null +++ b/modules/benchmarks/src/change_detection/BUILD.bazel @@ -0,0 +1,32 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "util_lib", + srcs = ["util.ts"], + tsconfig = "//modules/benchmarks:tsconfig-e2e.json", + deps = ["//modules/benchmarks/src:util_lib"], +) + +ts_library( + name = "perf_tests_lib", + testonly = 1, + srcs = ["change_detection.perf-spec.ts"], + tsconfig = "//modules/benchmarks:tsconfig-e2e.json", + deps = [ + "//modules/e2e_util", + "@npm//protractor", + ], +) + +ts_library( + name = "e2e_tests_lib", + testonly = 1, + srcs = ["change_detection.e2e-spec.ts"], + tsconfig = "//modules/benchmarks:tsconfig-e2e.json", + deps = [ + "//modules/e2e_util", + "@npm//protractor", + ], +) diff --git a/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts b/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts new file mode 100644 index 0000000000..bbbf8afed9 --- /dev/null +++ b/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts @@ -0,0 +1,30 @@ +/** + * @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 {$} from 'protractor'; + +import {openBrowser, verifyNoBrowserErrors} from '../../../e2e_util/e2e_util'; + +describe('change detection benchmark', () => { + afterEach(verifyNoBrowserErrors); + + it(`should render and update`, async() => { + openBrowser({ + url: '', + ignoreBrowserSynchronization: true, + params: [{name: 'viewCount', value: 1}], + }); + expect($('#root').getText()).toContain('1'); + await $('#detectChanges').click(); + expect($('#root').getText()).toContain('2'); + await $('#detectChanges').click(); + expect($('#root').getText()).toContain('3'); + await $('#destroyDom').click(); + expect(await $('#root').getText()).toEqual(''); + }); +}); diff --git a/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts b/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts new file mode 100644 index 0000000000..6251659446 --- /dev/null +++ b/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts @@ -0,0 +1,64 @@ +/** + * @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 {$} from 'protractor'; +import {runBenchmark, verifyNoBrowserErrors} from '../../../e2e_util/perf_util'; + +interface Worker { + id: string; + prepare?(): void; + work(): void; +} + +const UpdateWorker: Worker = { + id: 'createOnly', + prepare: () => { + $('#destroyDom').click(); + $('#createDom').click(); + }, + work: () => $('#detectChanges').click() +}; + + +// In order to make sure that we don't change the ids of the benchmarks, we need to +// determine the current test package name from the Bazel target. This is necessary +// because previous to the Bazel conversion, the benchmark test ids contained the test +// name. We determine the name of the Bazel package where this test runs from the current test +// target. The Bazel target +// looks like: "//modules/benchmarks/src/change_detection/{pkg_name}:{target_name}". +const testPackageName = process.env['BAZEL_TARGET'] !.split(':')[0].split('/').pop(); + +describe('change detection benchmark perf', () => { + + afterEach(verifyNoBrowserErrors); + + [UpdateWorker].forEach((worker) => { + describe(worker.id, () => { + it(`should run benchmark for ${testPackageName}`, async() => { + await runChangeDetectionBenchmark({ + id: `change_detection.${testPackageName}.${worker.id}`, + url: '/', + ignoreBrowserSynchronization: true, + worker: worker + }); + }); + }); + }); +}); + +function runChangeDetectionBenchmark( + config: {id: string, url: string, ignoreBrowserSynchronization?: boolean, worker: Worker}) { + return runBenchmark({ + id: config.id, + url: config.url, + ignoreBrowserSynchronization: config.ignoreBrowserSynchronization, + params: [{name: 'viewCount', value: 10}], + prepare: config.worker.prepare, + work: config.worker.work + }); +} diff --git a/modules/benchmarks/src/change_detection/transplanted_views/BUILD.bazel b/modules/benchmarks/src/change_detection/transplanted_views/BUILD.bazel new file mode 100644 index 0000000000..0ee15f5b96 --- /dev/null +++ b/modules/benchmarks/src/change_detection/transplanted_views/BUILD.bazel @@ -0,0 +1,55 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle", "ts_devserver") +load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test") +load("//modules/benchmarks:e2e_test.bzl", "e2e_test") + +ng_module( + name = "transplanted_views_lib", + srcs = [ + "index_aot.ts", + "transplanted_views.ts", + ], + tags = ["ivy-only"], + deps = [ + "//modules/benchmarks/src:util_lib", + "//modules/benchmarks/src/change_detection:util_lib", + "//packages:types", + "//packages/common", + "//packages/core", + ], +) + +ng_rollup_bundle( + name = "bundle", + entry_point = ":index_aot.ts", + tags = ["ivy-only"], + deps = [ + ":transplanted_views_lib", + "@npm//rxjs", + ], +) + +ts_devserver( + name = "devserver", + port = 4200, + static_files = ["index.html"], + tags = ["ivy-only"], + deps = [ + ":bundle.min_debug.js", + ], +) + +benchmark_test( + name = "perf", + server = ":devserver", + tags = ["ivy-only"], + deps = ["//modules/benchmarks/src/change_detection:perf_tests_lib"], +) + +e2e_test( + name = "e2e", + server = ":devserver", + tags = ["ivy-only"], + deps = ["//modules/benchmarks/src/change_detection:e2e_tests_lib"], +) diff --git a/modules/benchmarks/src/change_detection/transplanted_views/index.html b/modules/benchmarks/src/change_detection/transplanted_views/index.html new file mode 100644 index 0000000000..f75c7fc447 --- /dev/null +++ b/modules/benchmarks/src/change_detection/transplanted_views/index.html @@ -0,0 +1,32 @@ + + + + + + + + +

Params

+
+ View Count: + +
+ +
+ +

Render3 Transplanted View Benchmark

+

+ + + + +

+ +
+ +
+ + + + + diff --git a/modules/benchmarks/src/change_detection/transplanted_views/index_aot.ts b/modules/benchmarks/src/change_detection/transplanted_views/index_aot.ts new file mode 100644 index 0000000000..60c4de5e7e --- /dev/null +++ b/modules/benchmarks/src/change_detection/transplanted_views/index_aot.ts @@ -0,0 +1,28 @@ +/** + * @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 {ɵrenderComponent as renderComponent} from '@angular/core'; + +import {bindAction, profile} from '../../util'; + +import {DeclarationComponent, createDom, destroyDom, detectChanges} from './transplanted_views'; + +function noop() {} + +export function main() { + let component: DeclarationComponent; + if (typeof window !== 'undefined') { + component = renderComponent(DeclarationComponent); + bindAction('#destroyDom', () => destroyDom(component)); + bindAction('#createDom', () => createDom(component)); + bindAction('#detectChanges', () => detectChanges(component)); + bindAction( + '#detectChangesProfile', profile(() => detectChanges(component), noop, 'detect_changes')); + } +} + +main(); diff --git a/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts b/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts new file mode 100644 index 0000000000..c02809f4ee --- /dev/null +++ b/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts @@ -0,0 +1,65 @@ +/** + * @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 {CommonModule} from '@angular/common'; +import {ChangeDetectionStrategy, Component, Input, NgModule, TemplateRef, ɵdetectChanges} from '@angular/core'; +import {newArray, numViews} from '../util'; + +@Component({ + selector: 'declaration-component', + template: ` + {{trackTemplateRefresh()}} + + `, +}) +export class DeclarationComponent { + @Input() viewCount = 1; + // Tracks number of times the template was executed to ensure it was updated during CD. + templateRefreshCount = 0; + + trackTemplateRefresh() { + this.templateRefreshCount++; + return this.templateRefreshCount; + } +} + +@Component({ + selector: 'insertion-component', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InsertionComponent { + @Input() template !: TemplateRef<{}>; + views: any[] = []; + @Input() + set viewCount(n: number) { this.views = n > 0 ? newArray(n) : []; } + + // use trackBy to ensure profile isn't affected by the cost to refresh ngFor. + trackByIndex(index: number, item: any) { return index; } +} + +@NgModule({declarations: [DeclarationComponent, InsertionComponent], imports: [CommonModule]}) +export class TransplantedViewModule { +} + +export function destroyDom(component: DeclarationComponent) { + component.templateRefreshCount = 0; + component.viewCount = 0; + ɵdetectChanges(component); +} + +export function createDom(component: DeclarationComponent) { + component.viewCount = numViews; + ɵdetectChanges(component); +} + +export function detectChanges(component: DeclarationComponent) { + ɵdetectChanges(component); +} diff --git a/modules/benchmarks/src/change_detection/util.ts b/modules/benchmarks/src/change_detection/util.ts new file mode 100644 index 0000000000..7205f72454 --- /dev/null +++ b/modules/benchmarks/src/change_detection/util.ts @@ -0,0 +1,21 @@ +/** + * @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 {getIntParameter} from '../util'; + +export const numViews = getIntParameter('viewCount'); + +export function newArray(size: number): T[]; +export function newArray(size: number, value: T): T[]; +export function newArray(size: number, value?: T): T[] { + const list: T[] = []; + for (let i = 0; i < size; i++) { + list.push(value !); + } + return list; +}