Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(ivy): add benchmark for [class]=exp use case - fixup #33375

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/core/test/render3/perf/BUILD.bazel
Expand Up @@ -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",
Expand Down
165 changes: 165 additions & 0 deletions 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<any>) {
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();
}

`<div [class]="toggleClasses ? CLASSES_1_A : CLASSES_1_B">`;
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);
}
});


`<div [class]="toggleClasses ? CLASSES_2_A : CLASSES_2_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);
}
});


`<div [class]="toggleClasses ? CLASSES_10_A : CLASSES_10_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);
}
});


`<div class="A B">`;
benchmark(`class="A B"`, function(rf: RenderFlags, ctx: any) {
if (rf & 1) {
ɵɵelement(0, 'div', 0);
}
if (rf & 2) {
}
});


`<div class="A B"
[class]="toggleClasses ? CLASSES_1_A : CLASSES_1_B">`;
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);
}
});


`<div class="A B"
[class]="toggleClasses ? CLASSES_10_A : CLASSES_10_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);
}
});

`<div class="A B"
[class]="toggleClasses ? CLASSES_1_A : CLASSES_1_B"
[class.foo]="toggleClasses">`;
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);
}
});

`<div class="A B"
[class]="toggleClasses ? CLASSES_10_A : CLASSES_10_B"
[class.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);
}
});


`<div [className]="toggleClasses ? CLASSES_10_A : CLASSES_10_B">`;
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();
68 changes: 49 additions & 19 deletions packages/core/test/render3/perf/setup.ts
Expand Up @@ -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';
Expand All @@ -25,33 +25,63 @@ export function createAndRenderLView(
export function setupRootViewWithEmbeddedViews(
templateFn: ComponentTemplate<any>| 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<any>| 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,
};
}