Skip to content

Commit abe7208

Browse files
test(html): benchmark each printer fixture unminimized too
A build that is not minimizing still parses and walks, and that pass is the floor the printer sits on. Running every fixture both ways makes the pair the measurement: the difference is what printing costs on that shape, which neither number shows alone. fixture walk minify document 25.6 -> 61.2 ms fragment (tr) 13.4 -> 27.9 ms wide table 15.4 -> 32.8 ms flat siblings 6.7 -> 13.6 ms nested lists 10.8 -> 23.5 ms deep nesting 12.7 -> 26.9 ms composed subtrees 7.2 -> 15.8 ms implied start tags 3.9 -> 9.9 ms The fixtures move to a table so the two modes cannot drift apart; the `minify (...)` names are unchanged, so their history carries over.
1 parent d4ab21f commit abe7208

1 file changed

Lines changed: 109 additions & 114 deletions

File tree

‎test/benchmarkCases/html-printer-document-unit/index.bench.mjs‎

Lines changed: 109 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ const { SourceProcessor } = htmlSyntax;
1414
// children's text and read back whole. Both paths are covered here, and so are
1515
// the tree shapes they cost differently on — the piecemeal path carries one
1616
// frame per open element, so fan-out and depth are separate axes.
17+
//
18+
// Every fixture runs twice: `walk` is what a build that is not minimizing pays
19+
// — parse and walk, no output — and `minify` is the same pass with the printer
20+
// on. No visitors are registered, so `walk` is the floor both share and the
21+
// difference is what printing costs on that shape, which neither number shows
22+
// alone. Visitor cost itself is `html-parser-document-unit`'s.
1723

1824
// No large real-world HTML ships in the repo, so generate a deterministic
1925
// document mixing tree construction, entity references, comments, lists/tables
@@ -39,125 +45,114 @@ function makeHtml(blocks) {
3945
}
4046
return `${out}</body></html>`;
4147
}
42-
const HTML = makeHtml(2000);
4348

44-
// Big table-body fragment (tr context seeds the tokenizer content mode).
45-
const FRAGMENT = '<td class="c">cell <b>one</b> &amp; two</td>'.repeat(6000);
46-
47-
// One axis each. `TABLE` and `LISTS` are wide with a shallow repeating nest,
48-
// `FLAT` is one long sibling run at depth 1, `DEEP` is a narrow 20-deep spine —
49-
// together they separate the per-sibling cost from the per-frame one.
50-
const TABLE = (() => {
51-
let s = "<table>";
52-
for (let i = 0; i < 8000; i++) {
53-
s += `<tr><td class=c${i}>cell ${i}</td><td>${i}</td></tr>`;
54-
}
55-
return `${s}</table>`;
56-
})();
57-
const FLAT = (() => {
58-
let s = "";
59-
for (let i = 0; i < 8000; i++) s += `<p class=c${i}>text ${i}</p>`;
60-
return s;
61-
})();
62-
const LISTS = (() => {
63-
let s = "";
64-
for (let i = 0; i < 800; i++) {
65-
s += `<ul id=u${i}>`;
66-
for (let j = 0; j < 10; j++) s += `<li><a href=/p${j}>item ${j}</a></li>`;
67-
s += "</ul>";
68-
}
69-
return s;
70-
})();
71-
const DEEP = (() => {
72-
let s = "";
73-
for (let i = 0; i < 800; i++) {
74-
for (let j = 0; j < 20; j++) s += `<div class=l${j}>`;
75-
s += `<span>leaf ${i}</span>`;
76-
for (let j = 0; j < 20; j++) s += "</div>";
77-
}
78-
return s;
79-
})();
80-
81-
// Elements that cannot print in pieces, so their subtrees are composed and read
82-
// back: a `<pre>` / `<textarea>` leading newline only survives re-parsing once
83-
// the children's text is in, and a `<template>` holds its children in a content
84-
// fragment rather than the child chain.
85-
const WHOLE = (() => {
86-
let s = "";
87-
for (let i = 0; i < 2000; i++) {
88-
s += `<div><pre>\n\nkeep ${i}</pre><textarea>\n\nt ${i}</textarea><template><p>x${i}</p></template></div>`;
89-
}
90-
return s;
91-
})();
92-
93-
// An omitted `<html>` / `<body>` start tag, held back until the first thing
94-
// inside it decides whether it materializes (it does here — the text is
95-
// whitespace the insertion modes would otherwise drop).
96-
const IMPLIED = (() => {
97-
let s = "";
98-
for (let i = 0; i < 4000; i++) s += `<p>t${i}<b>b${i}</b>`;
99-
return ` ${s}`;
100-
})();
49+
// One axis each. `wide table` and `nested lists` are wide with a shallow
50+
// repeating nest, `flat siblings` is one long sibling run at depth 1, `deep
51+
// nesting` is a narrow 20-deep spine — together they separate the per-sibling
52+
// cost from the per-frame one.
53+
/** @type {[string, string, { fragmentContext?: string }][]} name, source, extra process options */
54+
const FIXTURES = [
55+
["document", makeHtml(2000), {}],
56+
// The context element seeds the tokenizer content mode.
57+
[
58+
"fragment, tr context",
59+
'<td class="c">cell <b>one</b> &amp; two</td>'.repeat(6000),
60+
{ fragmentContext: "tr" }
61+
],
62+
[
63+
"wide table",
64+
(() => {
65+
let s = "<table>";
66+
for (let i = 0; i < 8000; i++) {
67+
s += `<tr><td class=c${i}>cell ${i}</td><td>${i}</td></tr>`;
68+
}
69+
return `${s}</table>`;
70+
})(),
71+
{}
72+
],
73+
[
74+
"flat siblings",
75+
(() => {
76+
let s = "";
77+
for (let i = 0; i < 8000; i++) s += `<p class=c${i}>text ${i}</p>`;
78+
return s;
79+
})(),
80+
{}
81+
],
82+
[
83+
"nested lists",
84+
(() => {
85+
let s = "";
86+
for (let i = 0; i < 800; i++) {
87+
s += `<ul id=u${i}>`;
88+
for (let j = 0; j < 10; j++) {
89+
s += `<li><a href=/p${j}>item ${j}</a></li>`;
90+
}
91+
s += "</ul>";
92+
}
93+
return s;
94+
})(),
95+
{}
96+
],
97+
[
98+
"deep nesting",
99+
(() => {
100+
let s = "";
101+
for (let i = 0; i < 800; i++) {
102+
for (let j = 0; j < 20; j++) s += `<div class=l${j}>`;
103+
s += `<span>leaf ${i}</span>`;
104+
for (let j = 0; j < 20; j++) s += "</div>";
105+
}
106+
return s;
107+
})(),
108+
{}
109+
],
110+
// Elements that cannot print in pieces, so their subtrees are composed and
111+
// read back: a `<pre>` / `<textarea>` leading newline only survives
112+
// re-parsing once the children's text is in, and a `<template>` holds its
113+
// children in a content fragment rather than the child chain.
114+
[
115+
"composed subtrees",
116+
(() => {
117+
let s = "";
118+
for (let i = 0; i < 2000; i++) {
119+
s += `<div><pre>\n\nkeep ${i}</pre><textarea>\n\nt ${i}</textarea><template><p>x${i}</p></template></div>`;
120+
}
121+
return s;
122+
})(),
123+
{}
124+
],
125+
// An omitted `<html>` / `<body>` start tag, held back until the first thing
126+
// inside it decides whether it materializes (it does here — the text is
127+
// whitespace the insertion modes would otherwise drop).
128+
[
129+
"implied start tags",
130+
(() => {
131+
let s = " ";
132+
for (let i = 0; i < 4000; i++) s += `<p>t${i}<b>b${i}</b>`;
133+
return s;
134+
})(),
135+
{}
136+
]
137+
];
101138

102139
/**
103140
* @param {import("tinybench").Bench} bench bench
104141
* @returns {void}
105142
*/
106143
export default (bench) => {
107-
// Whole-document minify — the entry `htmlMinify` drives.
108-
bench.add(
109-
'unit benchmark "html-printer-document-unit", minify (document)',
110-
() => {
111-
new SourceProcessor().process(HTML, { minimize: true });
112-
}
113-
);
114-
bench.add(
115-
'unit benchmark "html-printer-document-unit", minify (fragment, tr context)',
116-
() => {
117-
new SourceProcessor().process(FRAGMENT, {
118-
minimize: true,
119-
fragmentContext: "tr"
120-
});
121-
}
122-
);
123-
124-
// Tree shapes: wide, flat, nested-wide, deep.
125-
bench.add(
126-
'unit benchmark "html-printer-document-unit", minify (wide table)',
127-
() => {
128-
new SourceProcessor().process(TABLE, { minimize: true });
129-
}
130-
);
131-
bench.add(
132-
'unit benchmark "html-printer-document-unit", minify (flat siblings)',
133-
() => {
134-
new SourceProcessor().process(FLAT, { minimize: true });
135-
}
136-
);
137-
bench.add(
138-
'unit benchmark "html-printer-document-unit", minify (nested lists)',
139-
() => {
140-
new SourceProcessor().process(LISTS, { minimize: true });
141-
}
142-
);
143-
bench.add(
144-
'unit benchmark "html-printer-document-unit", minify (deep nesting)',
145-
() => {
146-
new SourceProcessor().process(DEEP, { minimize: true });
147-
}
148-
);
149-
150-
// The two paths a node can take, isolated.
151-
bench.add(
152-
'unit benchmark "html-printer-document-unit", minify (composed subtrees)',
153-
() => {
154-
new SourceProcessor().process(WHOLE, { minimize: true });
155-
}
156-
);
157-
bench.add(
158-
'unit benchmark "html-printer-document-unit", minify (implied start tags)',
159-
() => {
160-
new SourceProcessor().process(IMPLIED, { minimize: true });
161-
}
162-
);
144+
for (const [name, source, options] of FIXTURES) {
145+
bench.add(
146+
`unit benchmark "html-printer-document-unit", walk (${name})`,
147+
() => {
148+
new SourceProcessor().process(source, options);
149+
}
150+
);
151+
bench.add(
152+
`unit benchmark "html-printer-document-unit", minify (${name})`,
153+
() => {
154+
new SourceProcessor().process(source, { ...options, minimize: true });
155+
}
156+
);
157+
}
163158
};

0 commit comments

Comments
 (0)