8000 test(core): add micro benchmarks for i18n scenarios (#39142) · angular/angular@4207cff · GitHub
[go: up one dir, main page]

Skip to content

Commit 4207cff

Browse files
AndrewKushniratscott
authored andcommitted
test(core): add micro benchmarks for i18n scenarios (#39142)
This commit adds micro benchmarks to run micro benchmarks for i18n-related logic in the following scenarios: - i18n static attributes - i18n attributes with interpolations - i18n blocks of static text - i18n blocks of text + interpolations - simple ICUs - nested ICUs First 4 scenarios also have baseline scenarios (non-i18n) so that we can compare i18n perf with non-i18n logic. PR Close #39142
1 parent f591fc8 commit 4207cff

File tree

6 files changed

+386
-18
lines changed

6 files changed

+386
-18
lines changed

packages/core/src/render3/instructions/all.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ export * from './class_map_interpolation';
4848
export * from './style_map_interpolation';
4949
export * from './style_prop_interpolation';
5050
export * from './host_property';
51+
export * from './i18n';

packages/core/src/render3/instructions/i18n.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {HEADER_OFFSET} from '../interfaces/view';
1717
import {getLView, getTView, nextBindingIndex} from '../state';
1818
import {getConstant} from '../util/view_utils';
1919

20-
import {setDelayProjection} from './all';
20+
import {setDelayProjection} from './projection';
2121

2222
/**
2323
* Marks a block of text as translatable.

packages/core/test/render3/perf/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,19 @@ ng_benchmark(
230230
bundle = ":host_binding",
231231
)
232232

233+
ng_rollup_bundle(
234+
name = "i18n_lib",
235+
entry_point = ":i18n/index.ts",
236+
deps = [
237+
":perf_lib",
238+
],
239+
)
240+
241+
ng_benchmark(
242+
name = "i18n",
243+
bundle = ":i18n",
244+
)
245+
233246
ng_rollup_bundle(
234247
name = "view_destroy_hook_lib",
235248
entry_point = ":view_destroy_hook/index.ts",
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ɵɵadvance, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵi18n, ɵɵi18nApply, ɵɵi18nAttributes, ɵɵi18nExp, ɵɵpropertyInterpolate1, ɵɵtext, ɵɵtextInterpolate1} from '../../../../src/render3/instructions/all';
10+
import {ComponentTemplate, RenderFlags} from '../../../../src/render3/interfaces/definition';
11+
import {AttributeMarker, TAttributes} from '../../../../src/render3/interfaces/node';
12+
import {Benchmark, createBenchmark} from '../micro_bench';
13+
import {MicroBenchmarkRenderNode} from '../noop_renderer';
14+
import {setupTestHarness} from '../setup';
15+
16+
type ComponentDef = {
17+
consts: (string|TAttributes)[],
18+
vars: number,
19+
decls: number,
20+
template: ComponentTemplate<any>,
21+
beforeCD?: Function,
22+
DOMParserMockFn?: Function,
23+
};
24+
25+
const enum NodeTypes {
26+
ELEMENT_NODE = 1,
27+
TEXT_NODE = 2,
28+
COMMENT_NODE = 8
29+
}
30+
31+
function createElement(nodeType: number, tagName?: string): any {
32+
const element = new MicroBenchmarkRenderNode();
33+
element.nodeType = nodeType;
34+
element.tagName = tagName;
35+
return element;
36+
}
37+
38+
// Mock function that is invoked when the string should be parsed.
39+
function defaultDOMParserMockFn(content: string) {
40+
const element = createElement(NodeTypes.TEXT_NODE);
41+
element.textContent = content;
42+
return element;
43+
}
44+
45+
function createDOMParserMock(mockFn: Function) {
46+
return {
47+
parseFromString: (content: string) => {
48+
const body = createElement(NodeTypes.ELEMENT_NODE, 'body');
49+
content = content.replace(/<body><remove><\/remove>/, '');
50+
body.firstChild = mockFn(content);
51+
return {body};
< 878 /code>52+
},
53+
};
54+
}
55+
56+
function setupDOMParserMock(mockFn?: Function): Function {
57+
const glob = global as any;
58+
if (!glob.window) {
59+
glob.window = {};
60+
}
61+
const origDOMParser = glob.window.DOMParser;
62+
glob.window.DOMParser = function() {
63+
return createDOMParserMock(mockFn || defaultDOMParserMockFn);
64+
};
65+
66+
// Return a function that would restore DOMParser to its original state.
67+
return () => glob.window.DOMParser = origDOMParser;
68+
}
69+
70+
71+
const PROFILE_CREATE = true;
72+
const PROFILE_UPDATE = true;
73+
const NUM_OF_VIEWS_PER_RUN = 1000;
74+
const DEFAULT_CONTEXT: any = {
75+
title: 'Test title',
76+
interpolation: 'Test interpolation',
77+
count: 0
78+
};
79+
80+
let context = DEFAULT_CONTEXT;
81+
const benchmarks: Benchmark[] = [];
82+
83+
function benchmark(name: string, def: ComponentDef, baselineDef?: ComponentDef) {
84+
// Reset context in case it was changed in `beforeCD` function during the previous benchmark.
85+
context = DEFAULT_CONTEXT;
86+
87+
const teardownDOMParserMock = setupDOMParserMock(def.DOMParserMockFn);
88+
89+
const ivyHarness = setupTestHarness(
90+
def.template, def.decls, def.vars, NUM_OF_VIEWS_PER_RUN, context,
91+
def.consts as TAttributes[]);
92+
93+
let baseHarness;
94+
if (baselineDef) {
95+
baseHarness = setupTestHarness(
96+
baselineDef.template, baselineDef.decls, baselineDef.vars, NUM_OF_VIEWS_PER_RUN, context,
97+
baselineDef.consts as TAttributes[]);
98+
}
99+
100+
if (PROFILE_CREATE) {
101+
const benchmark = createBenchmark('i18n [create]: ' + name);
102+
benchmarks.push(benchmark);
103+
const ivyProfile = benchmark('(i18n)');
104+
console.profile(benchmark.name + ':' + ivyProfile.name);
105+
while (ivyProfile()) {
106+
ivyHarness.createEmbeddedLView();
107+
}
108+
console.profileEnd();
109+
110+
if (baseHarness) {
111+
const baseProfile = benchmark('(baseline)');
112+
console.profile(benchmark.name + ':' + baseProfile.name);
113+
while (baseProfile()) {
114+
baseHarness.createEmbeddedLView();
115+
}
116+
console.profileEnd();
117+
}
118+
}
119+
120+
if (PROFILE_UPDATE) {
121+
const benchmark = createBenchmark('i18n [update]: : ' + name);
122+
benchmarks.push(benchmark);
123+
const ivyProfile = benchmark('(i18n)');
124+
console.profile(benchmark.name + ':' + ivyProfile.name);
125+
while (ivyProfile()) {
126+
if (def.beforeCD) {
127+
def.beforeCD(context);
128+
}
129+
ivyHarness.detectChanges();
130+
}
131+
console.profileEnd();
132+
133+
if (baseHarness) {
134+
const baseProfile = benchmark('(baseline)');
135+
console.profile(benchmark.name + ':' + baseProfile.name);
136+
while (baseProfile()) {
137+
if (baselineDef && baselineDef.beforeCD) {
138+
baselineDef.beforeCD(context);
139+
}
140+
baseHarness.detectChanges();
141+
}
142+
console.profileEnd();
143+
}
144+
}
145+
146+
teardownDOMParserMock();
147+
}
148+
149+
benchmark(
150+
`Static attributes`,
151+
152+
// <div i18n-title title="Test Title"></div>
153+
{
154+
decls: 2,
155+
vars: 0,
156+
consts: [[AttributeMarker.I18n, 'title'], ['title', 'Test Title']],
157+
template: function(rf: RenderFlags, ctx: any) {
158+
if (rf & 1) {
159+
ɵɵelementStart(0, 'div', 0);
160+
ɵɵi18nAttributes(1, 1);
161+
ɵɵelementEnd();
162+
}
163+
}
164+
},
165+
166+
// <div title="Test Title"></div>
167+
{
168+
decls: 2,
169+
vars: 0,
170+
consts: [['title', 'Test Title']],
171+
template: function(rf: RenderFlags, ctx: any) {
172+
if (rf & 1) {
173+
ɵɵelement(0, 'div', 0);
174+
}
175+
}
176+
});
177+
178+
benchmark(
179+
`Attributes with interpolations`,
180+
181+
// <div i18n-title title="Test {{ title }}"></div>
182+
{
183+
decls: 2,
184+
vars: 1,
185+
consts: [[AttributeMarker.I18n, 'title'], ['title', 'Test �0�']],
186+
template: function(rf: RenderFlags, ctx: any) {
187+
if (rf & 1) {
188+
ɵɵelementStart(0, 'div', 0);
189+
ɵɵi18nAttributes(1, 1);
190+
ɵɵelementEnd();
191+
}
192+
if (rf & 2) {
193+
ɵɵi18nExp(ctx.title);
194+
ɵɵi18nApply(1);
195+
}
196+
}
197+
},
198+
199+
// <div title="Test {{ title }}"></div>
200+
{
201+
decls: 2,
202+
vars: 1,
203+
consts: [[AttributeMarker.Bindings, 'title']],
204+
template: function(rf: RenderFlags, ctx: any) {
205+
if (rf & 1) {
206+
ɵɵelement(0, 'div', 0);
207+
}
208+
if (rf & 2) {
209+
ɵɵpropertyInterpolate1('title', 'Test ', ctx.title, '');
210+
}
211+
}
212+
});
213+
214+
benchmark(
215+
`Block of static text`,
216+
217+
// <div i18n>Some text content</div>
218+
{
219+
decls: 2,
220+
vars: 0,
221+
consts: ['Some text content'],
222+
template: function(rf: RenderFlags, ctx: any) {
223+
if (rf & 1) {
224+
ɵɵelementStart(0, 'div');
225+
ɵɵi18n(1, 0);
226+
ɵɵelementEnd();
227+
}
228+
}
229+
},
230+
231+
// <div>Some text content</div>
232+
{
233+
decls: 2,
234+
vars: 0,
235+
consts: [],
236+
template: function(rf: RenderFlags, ctx: any) {
237+
if (rf & 1) {
238+
ɵɵelementStart(0, 'div');
239+
ɵɵtext(1, 'Some text content');
240+
ɵɵelementEnd();
241+
}
242+
}
243+
});
244+
245+
benchmark(
246+
`Block of text with interpolation`,
247+
248+
// <div i18n>Some text content with {{ interpolation }}</div>
249+
{
250+
decls: 2,
251+
vars: 1,
252+
consts: ['Some text content with �0�'],
253+
template: function(rf: RenderFlags, ctx: any) {
254+
if (rf & 1) {
255+
ɵɵelementStart(0, 'div');
256+
ɵɵi18n(1, 0);
257+
ɵɵelementEnd();
258+
}
259+
if (rf & 2) {
260+
ɵɵadvance(1);
261+
ɵɵi18nExp(ctx.interpolation);
262+
ɵɵi18nApply(1);
263+
}
264+
}
265+
},
266+
267+
// <div>Some text content with {{ interpolation }}</div>
268+
{
269+
decls: 2,
270+
vars: 1,
271+
consts: [],
272+
template: function(rf: RenderFlags, ctx: any) {
273+
if (rf & 1) {
274+
ɵɵelementStart(0, 'div');
275+
ɵɵtext(1);
276+
ɵɵelementEnd();
277+
}
278+
if (rf & 2) {
279+
ɵɵadvance(1);
280+
ɵɵtextInterpolate1('Some text content with ', ctx.interpolation, '');
281+
}
282+
}
283+
});
284+
285+
benchmark(
286+
`Simple ICU`,
287+
288+
// {count, plural, =1 {one} =2 {two} other {other}}
289+
{
290+
decls: 1,
291+
vars: 1,
292+
consts: ['{�0�, plural, =1 {one} =2 {two} other {other}}'],
293+
template: function(rf: RenderFlags, ctx: any) {
294+
if (rf & 1) {
295+
ɵɵi18n(0, 0);
296+
}
297+
if (rf & 2) {
298+
ɵɵi18nExp(ctx.count);
299+
ɵɵi18nApply(0);
300+
}
301+
},
302+
beforeCD: function(ctx: any) {
303+
// Switch values between [0, 1, 2] to trigger different ICU cases.
304+
ctx.count = (ctx.count + 1) % 3;
305+
},
306+
});
307+
308+
benchmark(
309+
`Nested ICUs`,
310+
311+
// {count, plural,
312+
// =1 {one}
313+
// =2 {two}
314+
// other { {count, plural, =0 {zero} other {other}} }}
315+
{
316+
decls: 1,
317+
vars: 2,
318+
consts: ['{�0�, plural, =1 {one} =2 {two} other { {�0�, plural, =0 {zero} other {other}} }}'],
319+
template: function(rf: RenderFlags, ctx: any) {
320+
if (rf & 1) {
321+
ɵɵi18n(0, 0);
322+
}
323+
if (rf & 2) {
324+
ɵɵi18nExp(ctx.count)(ctx.count);
325+
ɵɵi18nApply(0);
326+
}
327+
},
328+
beforeCD: function(ctx: any) {
329+
// Switch values between [0, 1, 2] to trigger different ICU cases.
330+
ctx.count = (ctx.count + 1) % 3;
331+
},
332+
DOMParserMockFn: (content: string) => {
333+
content = content.trim();
334+
// Nested ICUs are represented as comment nodes. If we come across one - create an element
335+
// with correct node type, otherwise - call default mock fn.
336+
if (content.startsWith('<!--')) {
337+
const element = createElement(NodeTypes.COMMENT_NODE);
338+
element.textContent = content;
339+
return element;
340+
}
341+
return defaultDOMParserMockFn(content);
342+
}
343+
});
344+
345+
benchmarks.forEach(b => b.report());

packages/core/test/render3/perf/noop_renderer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import {ProceduralRenderer3, RComment, RElement, Renderer3, RendererFactory3, RendererStyleFlags3, RNode, RText} from '../../../src/render3/interfaces/renderer';
99

1010
export class MicroBenchmarkRenderNode implements RNode, RComment, RText {
11+
tagName?: string;
12+
nodeType?: number;
1113
textContent: string|null = null;
1214
parentNode: RNode|null = null;
1315
parentElement: RElement|null = null;
@@ -43,7 +45,7 @@ export class MicroBenchmarkRenderer implements ProceduralRenderer3 {
4345
throw new Error('Method not implemented.');
4446
}
4547
parentNode(node: RNode): RElement|null {
46-
throw new Error('Method not implemented.');
48+
return null;
4749
}
4850
nextSibling(node: RNode): RNode|null {
4951
throw new Error('Method not implemented.');

0 commit comments

Comments
 (0)
0