From c76cac25eb5096c6aaedf015b6a2ebbebbfbf6cb Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sun, 15 Oct 2023 20:48:51 -0700 Subject: [PATCH] test(core): add benchmark for defer runtime logic (#52222) This commit adds a benchmark for `@defer` runtime logic and uses `@if` as a baseline. PR Close #52222 --- modules/benchmarks/src/defer/BUILD.bazel | 39 +++++++++++ modules/benchmarks/src/defer/README.md | 10 +++ .../benchmarks/src/defer/baseline/BUILD.bazel | 55 +++++++++++++++ .../src/defer/baseline/app.component.ts | 55 +++++++++++++++ .../benchmarks/src/defer/baseline/index.html | 39 +++++++++++ .../benchmarks/src/defer/baseline/index.ts | 21 ++++++ .../benchmarks/src/defer/defer.e2e-spec.ts | 24 +++++++ .../benchmarks/src/defer/defer.perf-spec.ts | 67 +++++++++++++++++++ modules/benchmarks/src/defer/init.ts | 52 ++++++++++++++ modules/benchmarks/src/defer/main/BUILD.bazel | 55 +++++++++++++++ .../src/defer/main/app.component.ts | 55 +++++++++++++++ modules/benchmarks/src/defer/main/index.html | 39 +++++++++++ modules/benchmarks/src/defer/main/index.ts | 21 ++++++ modules/benchmarks/src/defer/util.ts | 48 +++++++++++++ 14 files changed, 580 insertions(+) create mode 100644 modules/benchmarks/src/defer/BUILD.bazel create mode 100644 modules/benchmarks/src/defer/README.md create mode 100644 modules/benchmarks/src/defer/baseline/BUILD.bazel create mode 100644 modules/benchmarks/src/defer/baseline/app.component.ts create mode 100644 modules/benchmarks/src/defer/baseline/index.html create mode 100644 modules/benchmarks/src/defer/baseline/index.ts create mode 100644 modules/benchmarks/src/defer/defer.e2e-spec.ts create mode 100644 modules/benchmarks/src/defer/defer.perf-spec.ts create mode 100644 modules/benchmarks/src/defer/init.ts create mode 100644 modules/benchmarks/src/defer/main/BUILD.bazel create mode 100644 modules/benchmarks/src/defer/main/app.component.ts create mode 100644 modules/benchmarks/src/defer/main/index.html create mode 100644 modules/benchmarks/src/defer/main/index.ts create mode 100644 modules/benchmarks/src/defer/util.ts diff --git a/modules/benchmarks/src/defer/BUILD.bazel b/modules/benchmarks/src/defer/BUILD.bazel new file mode 100644 index 00000000000..152862cf510 --- /dev/null +++ b/modules/benchmarks/src/defer/BUILD.bazel @@ -0,0 +1,39 @@ +load("//tools:defaults.bzl", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "shared_lib", + srcs = [ + "init.ts", + "util.ts", + ], + tsconfig = "//modules/benchmarks:tsconfig-build.json", + deps = [ + "//modules/benchmarks/src:util_lib", + "//packages/core", + "//packages/platform-browser", + ], +) + +ts_library( + name = "perf_tests_lib", + testonly = 1, + srcs = ["defer.perf-spec.ts"], + tsconfig = "//modules/benchmarks:tsconfig-e2e.json", + deps = [ + "@npm//@angular/build-tooling/bazel/benchmark/driver-utilities", + "@npm//protractor", + ], +) + +ts_library( + name = "e2e_tests_lib", + testonly = 1, + srcs = ["defer.e2e-spec.ts"], + tsconfig = "//modules/benchmarks:tsconfig-e2e.json", + deps = [ + "@npm//@angular/build-tooling/bazel/benchmark/driver-utilities", + "@npm//protractor", + ], +) diff --git a/modules/benchmarks/src/defer/README.md b/modules/benchmarks/src/defer/README.md new file mode 100644 index 00000000000..d4d27bb19b4 --- /dev/null +++ b/modules/benchmarks/src/defer/README.md @@ -0,0 +1,10 @@ +# Defer benchmark + +This folder contains defer benchmark that tests the process of `@defer` block creation. + +There are 2 folders in this benchmark: + +* `baseline` - renders a component using an `@if` condition, we use it as a baseline +* `main` - the same code as the `baseline`, but instead of the `@if`, we use `@defer` to compare defer blocks against conditionals + +The benchmarks are based on `largetable` benchmarks. diff --git a/modules/benchmarks/src/defer/baseline/BUILD.bazel b/modules/benchmarks/src/defer/baseline/BUILD.bazel new file mode 100644 index 00000000000..3e76e83380f --- /dev/null +++ b/modules/benchmarks/src/defer/baseline/BUILD.bazel @@ -0,0 +1,55 @@ +load("//tools:defaults.bzl", "app_bundle", "http_server", "ng_module") +load("@npm//@angular/build-tooling/bazel/benchmark/component_benchmark:benchmark_test.bzl", "benchmark_test") +load("//modules/benchmarks:e2e_test.bzl", "e2e_test") + +package(default_visibility = ["//modules/benchmarks:__subpackages__"]) + +ng_module( + name = "main", + srcs = glob(["*.ts"]), + tsconfig = "//modules/benchmarks:tsconfig-build.json", + deps = [ + "//modules/benchmarks/src:util_lib", + "//modules/benchmarks/src/defer:shared_lib", + "//packages/core", + "//packages/platform-browser", + ], +) + +app_bundle( + name = "bundle", + entry_point = ":index.ts", + deps = [ + ":main", + "@npm//rxjs", + ], +) + +# The script needs to be called `app_bundle` for easier syncing into g3. +genrule( + name = "app_bundle", + srcs = [":bundle.debug.min.js"], + outs = ["app_bundle.js"], + cmd = "cp $< $@", +) + +http_server( + name = "prodserver", + srcs = ["index.html"], + deps = [ + ":app_bundle", + "//packages/zone.js/bundles:zone.umd.js", + ], +) + +benchmark_test( + name = "perf", + server = ":prodserver", + deps = ["//modules/benchmarks/src/defer:perf_tests_lib"], +) + +e2e_test( + name = "e2e", + server = ":prodserver", + deps = ["//modules/benchmarks/src/defer:e2e_tests_lib"], +) diff --git a/modules/benchmarks/src/defer/baseline/app.component.ts b/modules/benchmarks/src/defer/baseline/app.component.ts new file mode 100644 index 00000000000..f7972d3546f --- /dev/null +++ b/modules/benchmarks/src/defer/baseline/app.component.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright Google LLC 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 {Component, Input} from '@angular/core'; +import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; + +import {TableCell} from '../util'; + +let trustedEmptyColor: SafeStyle; +let trustedGreyColor: SafeStyle; + +@Component({ + standalone: true, + selector: 'app', + template: ` + + + @for (row of data; track $index) { + + @for (cell of row; track $index) { + + } + + } + +
+ @if (condition) { + + Cell + } +
+ `, +}) +export class AppComponent { + @Input() data: TableCell[][] = []; + + condition = true; + + constructor(sanitizer: DomSanitizer) { + trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('white'); + trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey'); + } + + getColor(row: number) { + return row % 2 ? trustedEmptyColor : trustedGreyColor; + } +} diff --git a/modules/benchmarks/src/defer/baseline/index.html b/modules/benchmarks/src/defer/baseline/index.html new file mode 100644 index 00000000000..58813fd74f7 --- /dev/null +++ b/modules/benchmarks/src/defer/baseline/index.html @@ -0,0 +1,39 @@ + + + + + + + + +

Params

+
+ Cols: + +
+ Rows: + +
+ +
+ +

Defer Benchmark (baseline)

+

+ + + + +

+ +
+ +
+ + + + + + + + + diff --git a/modules/benchmarks/src/defer/baseline/index.ts b/modules/benchmarks/src/defer/baseline/index.ts new file mode 100644 index 00000000000..c9cd17f0dbc --- /dev/null +++ b/modules/benchmarks/src/defer/baseline/index.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google LLC 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 {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser'; + +import {init, syncUrlParamsToForm} from '../init'; + +import {AppComponent} from './app.component'; + +syncUrlParamsToForm(); + +bootstrapApplication(AppComponent, { + providers: [ + provideProtractorTestingSupport(), + ], +}).then(init); diff --git a/modules/benchmarks/src/defer/defer.e2e-spec.ts b/modules/benchmarks/src/defer/defer.e2e-spec.ts new file mode 100644 index 00000000000..24af72a1ddc --- /dev/null +++ b/modules/benchmarks/src/defer/defer.e2e-spec.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Google LLC 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 {openBrowser, verifyNoBrowserErrors} from '@angular/build-tooling/bazel/benchmark/driver-utilities'; +import {$} from 'protractor'; + +describe('defer benchmark', () => { + afterEach(verifyNoBrowserErrors); + + it(`should render the table`, async () => { + openBrowser({ + url: '', + ignoreBrowserSynchronization: true, + params: [{name: 'cols', value: 5}, {name: 'rows', value: 5}], + }); + await $('#createDom').click(); + expect($('#root').getText()).toContain('Cell'); + }); +}); diff --git a/modules/benchmarks/src/defer/defer.perf-spec.ts b/modules/benchmarks/src/defer/defer.perf-spec.ts new file mode 100644 index 00000000000..262594daef1 --- /dev/null +++ b/modules/benchmarks/src/defer/defer.perf-spec.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright Google LLC 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 {runBenchmark, verifyNoBrowserErrors} from '@angular/build-tooling/bazel/benchmark/driver-utilities'; +import {$} from 'protractor'; + +interface Worker { + id: string; + prepare?(): void; + work(): void; +} + +const CreateWorker: Worker = { + id: 'create', + prepare: () => $('#destroyDom').click(), + work: () => $('#createDom').click() +}; + +const UpdateWorker: Worker = { + id: 'update', + prepare: () => { + $('#createDom').click(); + }, + work: () => $('#createDom').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. e.g. "largeTable.ng2_switch.createDestroy". 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/largetable/{pkg_name}:{target_name}". +const testPackageName = process.env['BAZEL_TARGET']!.split(':')[0].split('/').pop(); + +describe('defer benchmark perf', () => { + afterEach(verifyNoBrowserErrors); + + [CreateWorker, UpdateWorker].forEach((worker) => { + describe(worker.id, () => { + it(`should run benchmark for ${testPackageName}`, async () => { + await runTableBenchmark({ + id: `defer.${testPackageName}.${worker.id}`, + url: '/', + ignoreBrowserSynchronization: true, + worker, + }); + }); + }); + }); +}); + +function runTableBenchmark( + config: {id: string, url: string, ignoreBrowserSynchronization?: boolean, worker: Worker}) { + return runBenchmark({ + id: config.id, + url: config.url, + ignoreBrowserSynchronization: config.ignoreBrowserSynchronization, + params: [{name: 'cols', value: 40}, {name: 'rows', value: 200}], + prepare: config.worker.prepare, + work: config.worker.work + }); +} diff --git a/modules/benchmarks/src/defer/init.ts b/modules/benchmarks/src/defer/init.ts new file mode 100644 index 00000000000..85d98085a9a --- /dev/null +++ b/modules/benchmarks/src/defer/init.ts @@ -0,0 +1,52 @@ +/** + * @license + * Copyright Google LLC 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 {ApplicationRef} from '@angular/core'; + +import {bindAction, profile} from '../util'; + +import {buildTable, emptyTable, initTableUtils} from './util'; + +const DEFAULT_COLS_COUNT = '40'; +const DEFAULT_ROWS_COUNT = '200'; + +function getUrlParamValue(name: string): string|null { + const url = new URL(document.location.href); + return url.searchParams.get(name); +} + +export function syncUrlParamsToForm(): {cols: string, rows: string} { + let cols = getUrlParamValue('cols') ?? DEFAULT_COLS_COUNT; + let rows = getUrlParamValue('rows') ?? DEFAULT_ROWS_COUNT; + (document.getElementById('cols') as HTMLInputElement).value = cols; + (document.getElementById('rows') as HTMLInputElement).value = rows; + return {cols, rows}; +} + +export function init(appRef: ApplicationRef) { + const table = appRef.components[0].instance; + + function destroyDom() { + table.data = emptyTable; + appRef.tick(); + } + + function createDom() { + table.data = buildTable(); + appRef.tick(); + } + + function noop() {} + + initTableUtils(); + + bindAction('#destroyDom', destroyDom); + bindAction('#createDom', createDom); + bindAction('#createDomProfile', profile(createDom, destroyDom, 'create')); + bindAction('#updateDomProfile', profile(createDom, noop, 'update')); +} diff --git a/modules/benchmarks/src/defer/main/BUILD.bazel b/modules/benchmarks/src/defer/main/BUILD.bazel new file mode 100644 index 00000000000..3e76e83380f --- /dev/null +++ b/modules/benchmarks/src/defer/main/BUILD.bazel @@ -0,0 +1,55 @@ +load("//tools:defaults.bzl", "app_bundle", "http_server", "ng_module") +load("@npm//@angular/build-tooling/bazel/benchmark/component_benchmark:benchmark_test.bzl", "benchmark_test") +load("//modules/benchmarks:e2e_test.bzl", "e2e_test") + +package(default_visibility = ["//modules/benchmarks:__subpackages__"]) + +ng_module( + name = "main", + srcs = glob(["*.ts"]), + tsconfig = "//modules/benchmarks:tsconfig-build.json", + deps = [ + "//modules/benchmarks/src:util_lib", + "//modules/benchmarks/src/defer:shared_lib", + "//packages/core", + "//packages/platform-browser", + ], +) + +app_bundle( + name = "bundle", + entry_point = ":index.ts", + deps = [ + ":main", + "@npm//rxjs", + ], +) + +# The script needs to be called `app_bundle` for easier syncing into g3. +genrule( + name = "app_bundle", + srcs = [":bundle.debug.min.js"], + outs = ["app_bundle.js"], + cmd = "cp $< $@", +) + +http_server( + name = "prodserver", + srcs = ["index.html"], + deps = [ + ":app_bundle", + "//packages/zone.js/bundles:zone.umd.js", + ], +) + +benchmark_test( + name = "perf", + server = ":prodserver", + deps = ["//modules/benchmarks/src/defer:perf_tests_lib"], +) + +e2e_test( + name = "e2e", + server = ":prodserver", + deps = ["//modules/benchmarks/src/defer:e2e_tests_lib"], +) diff --git a/modules/benchmarks/src/defer/main/app.component.ts b/modules/benchmarks/src/defer/main/app.component.ts new file mode 100644 index 00000000000..3fea3ca4c70 --- /dev/null +++ b/modules/benchmarks/src/defer/main/app.component.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright Google LLC 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 {Component, Input} from '@angular/core'; +import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; + +import {TableCell} from '../util'; + +let trustedEmptyColor: SafeStyle; +let trustedGreyColor: SafeStyle; + +@Component({ + standalone: true, + selector: 'app', + template: ` + + + @for (row of data; track $index) { + + @for (cell of row; track $index) { + + } + + } + +
+ @defer (when condition; on immediate) { + + Cell + } +
+ `, +}) +export class AppComponent { + @Input() data: TableCell[][] = []; + + condition = true; + + constructor(sanitizer: DomSanitizer) { + trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('white'); + trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey'); + } + + getColor(row: number) { + return row % 2 ? trustedEmptyColor : trustedGreyColor; + } +} diff --git a/modules/benchmarks/src/defer/main/index.html b/modules/benchmarks/src/defer/main/index.html new file mode 100644 index 00000000000..a51e8447dce --- /dev/null +++ b/modules/benchmarks/src/defer/main/index.html @@ -0,0 +1,39 @@ + + + + + + + + +

Params

+
+ Cols: + +
+ Rows: + +
+ +
+ +

Defer Benchmark (main)

+

+ + + + +

+ +
+ +
+ + + + + + + + + diff --git a/modules/benchmarks/src/defer/main/index.ts b/modules/benchmarks/src/defer/main/index.ts new file mode 100644 index 00000000000..c9cd17f0dbc --- /dev/null +++ b/modules/benchmarks/src/defer/main/index.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google LLC 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 {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser'; + +import {init, syncUrlParamsToForm} from '../init'; + +import {AppComponent} from './app.component'; + +syncUrlParamsToForm(); + +bootstrapApplication(AppComponent, { + providers: [ + provideProtractorTestingSupport(), + ], +}).then(init); diff --git a/modules/benchmarks/src/defer/util.ts b/modules/benchmarks/src/defer/util.ts new file mode 100644 index 00000000000..a99c9df64a8 --- /dev/null +++ b/modules/benchmarks/src/defer/util.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright Google LLC 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 class TableCell { + constructor(public row: number, public col: number, public value: string) {} +} + +let tableCreateCount: number; +let maxRow: number; +let maxCol: number; +let numberData: TableCell[][]; +let charData: TableCell[][]; + +export function initTableUtils() { + maxRow = getIntParameter('rows'); + maxCol = getIntParameter('cols'); + tableCreateCount = 0; + numberData = []; + charData = []; + for (let r = 0; r < maxRow; r++) { + const numberRow: TableCell[] = []; + numberData.push(numberRow); + const charRow: TableCell[] = []; + charData.push(charRow); + for (let c = 0; c < maxCol; c++) { + numberRow.push(new TableCell(r, c, `${c}/${r}`)); + charRow.push(new TableCell(r, c, `${charValue(c)}/${charValue(r)}`)); + } + } +} + +function charValue(i: number): string { + return String.fromCharCode('A'.charCodeAt(0) + (i % 26)); +} + +export const emptyTable: TableCell[][] = []; + +export function buildTable(): TableCell[][] { + tableCreateCount++; + return tableCreateCount % 2 ? numberData : charData; +}