Skip to content

Commit a5c0a3f

Browse files
committed
bench: add mitata harness for @glimmer/syntax parse/normalize/precompile
Reproducible side-by-side benchmark of preprocess, normalize (ASTv1 → ASTv2), and full precompile() against a control worktree. Run via: pnpm bench:syntax # standalone pnpm bench:syntax -- --control-dir /path/to/main-checkout Size ladder matches the PR #21314 body (462 / 1494 / 4482 / 32868 chars, the last sized to match Discourse's admin-user/index.gjs). Fixtures live under bench/fixtures/; large and extra-large are medium × 3 / × 22. Emits mitata's standard ms/iter + boxplot + summary output, plus a µs/char summary table across the size ladder that makes the O(n²) → O(n) flattening visible at a glance.
1 parent 487d30b commit a5c0a3f

3 files changed

Lines changed: 94 additions & 3 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"scripts": {
3838
"actions-up": "pnpm dlx actions-up",
3939
"bench": "node ./bin/benchmark.mjs",
40+
"bench:syntax": "node --expose-gc --import @swc-node/register/esm-register packages/@glimmer/syntax/bench/syntax.bench.mjs",
4041
"build:js": "rollup --config",
4142
"build:types": "node types/publish.mjs",
4243
"build": "npm-run-all build:*",
@@ -122,6 +123,7 @@
122123
"glob": "^8.0.3",
123124
"globals": "^16.0.0",
124125
"kill-port-process": "^3.2.1",
126+
"mitata": "^1.0.34",
125127
"mocha": "^11.0.0",
126128
"npm-run-all2": "^8.0.0",
127129
"prettier": "^3.5.3",
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Mitata benchmark for `@glimmer/syntax` parse (`preprocess`), normalize
3+
* (ASTv1 → ASTv2 — where the loc-conversion hot path lives), and full
4+
* `@glimmer/compiler` `precompile()`.
5+
*
6+
* Run:
7+
* pnpm bench:syntax
8+
*
9+
* To compare branches, check out each branch and run the bench; mitata will
10+
* print ms/iter for every phase × size.
11+
*
12+
* Sizes:
13+
* small — ~1.5k chars (route-template fragment)
14+
* medium — small × 3 (~4.5k chars)
15+
* large — small × 22 (~33k chars, scale of the largest real route
16+
* templates, e.g. Discourse's admin-user/index.gjs)
17+
*/
18+
19+
import { bench, do_not_optimize as doNotOptimize, run } from 'mitata';
20+
21+
import { normalize, preprocess, src } from '../index.ts';
22+
import { precompile } from '../../compiler/index.ts';
23+
24+
const SMALL = `<div class='user-profile {{if this.isPremium "premium"}}'>
25+
<header class='profile-header'>
26+
<img src={{this.avatarUrl}} alt={{this.username}} class='avatar' />
27+
<h2>{{this.displayName}}</h2>
28+
<p class='bio'>{{this.bio}}</p>
29+
{{#if this.isOwnProfile}}
30+
<button {{on 'click' this.editProfile}}>Edit Profile</button>
31+
{{/if}}
32+
</header>
33+
<nav class='profile-tabs'>
34+
{{#each this.tabs as |tab|}}
35+
<button
36+
class='tab {{if (eq tab.id this.activeTab) "active"}}'
37+
{{on 'click' (fn this.setTab tab.id)}}
38+
>
39+
{{tab.label}}{{#if tab.count}}<span class='count'>{{tab.count}}</span>{{/if}}
40+
</button>
41+
{{/each}}
42+
</nav>
43+
<section class='profile-content'>
44+
{{#if (eq this.activeTab 'posts')}}
45+
{{#each this.posts as |post|}}
46+
<article class='post-card'>
47+
<h3>{{post.title}}</h3><p>{{post.excerpt}}</p>
48+
<footer><time>{{post.createdAt}}</time><span>{{post.views}} views</span></footer>
49+
</article>
50+
{{else}}
51+
<p class='empty-state'>No posts yet.</p>
52+
{{/each}}
53+
{{else if (eq this.activeTab 'followers')}}
54+
{{#each this.followers as |follower|}}
55+
<div class='follower-card'>
56+
<img src={{follower.avatar}} alt={{follower.name}} />
57+
<span>{{follower.name}}</span>
58+
<button {{on 'click' (fn this.followUser follower.id)}}>
59+
{{if follower.isFollowing 'Unfollow' 'Follow'}}
60+
</button>
61+
</div>
62+
{{/each}}
63+
{{/if}}
64+
</section>
65+
</div>
66+
`;
67+
68+
const FIXTURES = {
69+
small: SMALL,
70+
medium: SMALL.repeat(3),
71+
large: SMALL.repeat(22),
72+
};
73+
74+
for (const [size, source] of Object.entries(FIXTURES)) {
75+
const chars = source.length;
76+
bench(`parse ${size} (${chars}c)`, () => doNotOptimize(preprocess(source)));
77+
bench(`normalize ${size} (${chars}c)`, () => doNotOptimize(normalize(new src.Source(source))));
78+
bench(`precompile ${size} (${chars}c)`, () => doNotOptimize(precompile(source)));
79+
}
80+
81+
await run({ throw: true });

pnpm-lock.yaml

Lines changed: 11 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)