Skip to content

Commit 2465689

Browse files
committed
Address comments
1 parent 7e0c8fc commit 2465689

8 files changed

Lines changed: 110 additions & 72 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ node_modules
1414
# Generated files
1515
/coverage/
1616
/dist/
17-
/bench/results/
17+
/tests/bench/results/
1818
/tests/integration/*/dist/
1919
/spec/tmp/*

lib/handlebars/compiler/javascript-compiler.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ JavaScriptCompiler.prototype = {
173173
}
174174
}
175175

176+
// Release AST/compiler references only needed during compilation for dedup
177+
this.context.environments.length = 0;
178+
176179
if (this.environment.usePartial) {
177180
ret.usePartial = true;
178181
}

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@
5151
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
5252
"test:serve": "npx serve -l 9999 .",
5353
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
54-
"bench": "node bench/perf.mjs",
55-
"bench:compare": "node bench/compare.mjs",
56-
"bench:size": "node bench/size.mjs",
54+
"bench": "node tests/bench/perf.mjs",
55+
"bench:compare": "node tests/bench/compare.mjs",
56+
"bench:size": "node tests/bench/size.mjs",
5757
"--- combined tasks ---": "",
5858
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
5959
},

bench/perf.mjs renamed to tests/bench/perf.mjs

Lines changed: 94 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { execSync } from 'node:child_process';
22
import { Bench } from 'tinybench';
3-
import Handlebars from '../lib/index.js';
3+
import Handlebars from '../../lib/index.js';
44
import { templates as allTemplates } from './templates.mjs';
55
import {
66
printResults,
@@ -9,8 +9,16 @@ import {
99
} from './report.mjs';
1010

1111
// ─── Configuration ───────────────────────────────────────────────────────────
12+
// Compilation happens once at app startup — low warmup reflects real-world conditions.
13+
// Execution happens thousands of times — higher warmup lets V8 optimize hot paths.
1214

13-
const BENCH_CONFIG = {
15+
const COMPILE_BENCH_CONFIG = {
16+
warmupIterations: 10,
17+
iterations: 1000,
18+
time: 3000,
19+
};
20+
21+
const EXEC_BENCH_CONFIG = {
1422
warmupIterations: 500,
1523
iterations: 5000,
1624
time: 3000,
@@ -67,8 +75,8 @@ if (grepPattern) {
6775

6876
// ─── Bench helpers ───────────────────────────────────────────────────────────
6977

70-
function newBench() {
71-
return new Bench(BENCH_CONFIG);
78+
function newBench(config) {
79+
return new Bench(config);
7280
}
7381

7482
function createEnv(def) {
@@ -84,10 +92,10 @@ function createEnv(def) {
8492
return hb;
8593
}
8694

87-
async function runSection(title, setup) {
95+
async function runSection(title, config, setup) {
8896
printSectionHeader(title);
8997

90-
const bench = newBench();
98+
const bench = newBench(config);
9199
setup(bench);
92100
await bench.run();
93101
printResults(bench);
@@ -105,7 +113,7 @@ async function run() {
105113
console.log(`Handlebars Performance Benchmark${headerLabel}`);
106114
console.log('================================');
107115
console.log(
108-
`tinybench | warmup: ${BENCH_CONFIG.warmupIterations} | minIterations: ${BENCH_CONFIG.iterations} | time: ${BENCH_CONFIG.time}ms per bench`
116+
`tinybench | compile: warmup ${COMPILE_BENCH_CONFIG.warmupIterations}, min ${COMPILE_BENCH_CONFIG.iterations} | exec: warmup ${EXEC_BENCH_CONFIG.warmupIterations}, min ${EXEC_BENCH_CONFIG.iterations} | time: ${EXEC_BENCH_CONFIG.time}ms per bench`
109117
);
110118
console.log(`Node ${process.version} | ${process.platform} ${process.arch}`);
111119
console.log();
@@ -115,30 +123,38 @@ async function run() {
115123
// ─── COMPILATION ────────────────────────────────────────────────────────────
116124

117125
allSections.push(
118-
await runSection('COMPILATION (Handlebars.compile)', (bench) => {
119-
for (const [name, def] of Object.entries(templates)) {
120-
const hb = createEnv(def);
121-
bench.add(`compile: ${name}`, () => {
122-
hb.compile(def.template);
123-
});
126+
await runSection(
127+
'COMPILATION (Handlebars.precompile)',
128+
COMPILE_BENCH_CONFIG,
129+
(bench) => {
130+
for (const [name, def] of Object.entries(templates)) {
131+
const hb = createEnv(def);
132+
bench.add(`compile: ${name}`, () => {
133+
hb.precompile(def.template);
134+
});
135+
}
124136
}
125-
})
137+
)
126138
);
127139

128140
// ─── EXECUTION + output verification ────────────────────────────────────────
129141

130142
const expectedOutputs = {};
131143

132144
allSections.push(
133-
await runSection('EXECUTION (template rendering)', (bench) => {
134-
for (const [name, def] of Object.entries(templates)) {
135-
const compiled = createEnv(def).compile(def.template);
136-
expectedOutputs[name] = compiled(def.context);
137-
bench.add(`exec: ${name}`, () => {
138-
compiled(def.context);
139-
});
145+
await runSection(
146+
'EXECUTION (template rendering)',
147+
EXEC_BENCH_CONFIG,
148+
(bench) => {
149+
for (const [name, def] of Object.entries(templates)) {
150+
const compiled = createEnv(def).compile(def.template);
151+
expectedOutputs[name] = compiled(def.context);
152+
bench.add(`exec: ${name}`, () => {
153+
compiled(def.context);
154+
});
155+
}
140156
}
141-
})
157+
)
142158
);
143159

144160
// Verify outputs haven't changed during benchmarking
@@ -163,65 +179,78 @@ async function run() {
163179
// ─── PRECOMPILATION ─────────────────────────────────────────────────────────
164180

165181
allSections.push(
166-
await runSection('PRECOMPILATION (Handlebars.precompile)', (bench) => {
167-
for (const [name, def] of Object.entries(templates)) {
168-
bench.add(`precompile: ${name}`, () => {
169-
Handlebars.precompile(def.template);
170-
});
182+
await runSection(
183+
'PRECOMPILATION (Handlebars.precompile)',
184+
COMPILE_BENCH_CONFIG,
185+
(bench) => {
186+
for (const [name, def] of Object.entries(templates)) {
187+
bench.add(`precompile: ${name}`, () => {
188+
Handlebars.precompile(def.template);
189+
});
190+
}
171191
}
172-
})
192+
)
173193
);
174194

175195
// ─── END-TO-END ─────────────────────────────────────────────────────────────
176196

177197
allSections.push(
178-
await runSection('END-TO-END (compile + render)', (bench) => {
179-
for (const [name, def] of Object.entries(templates)) {
180-
const hb = createEnv(def);
181-
bench.add(`e2e: ${name}`, () => {
182-
const fn = hb.compile(def.template);
183-
fn(def.context);
184-
});
198+
await runSection(
199+
'END-TO-END (compile + render)',
200+
COMPILE_BENCH_CONFIG,
201+
(bench) => {
202+
for (const [name, def] of Object.entries(templates)) {
203+
const hb = createEnv(def);
204+
bench.add(`e2e: ${name}`, () => {
205+
const fn = hb.compile(def.template);
206+
fn(def.context);
207+
});
208+
}
185209
}
186-
})
210+
)
187211
);
188212

189213
// ─── COMPILE OPTIONS ───────────────────────────────────────────────────────
190214

191215
allSections.push(
192-
await runSection('COMPILE OPTIONS COMPARISON', (bench) => {
193-
const src = allTemplates['complex (if/each/helpers)'].template;
194-
const ctx = allTemplates['complex (if/each/helpers)'].context;
195-
196-
const defaultFn = Handlebars.compile(src);
197-
bench.add('exec: default options', () => defaultFn(ctx));
198-
199-
const noEscapeFn = Handlebars.compile(src, { noEscape: true });
200-
bench.add('exec: noEscape=true', () => noEscapeFn(ctx));
201-
202-
const strictFn = Handlebars.compile(src, {
203-
strict: true,
204-
assumeObjects: true,
205-
});
206-
bench.add('exec: strict + assumeObjects', () => strictFn(ctx));
207-
208-
const knownFn = Handlebars.compile(src, {
209-
knownHelpers: { if: true, each: true },
210-
knownHelpersOnly: false,
211-
});
212-
bench.add('exec: knownHelpers', () => knownFn(ctx));
213-
214-
const compatFn = Handlebars.compile(src, { compat: true });
215-
bench.add('exec: compat=true', () => compatFn(ctx));
216-
217-
const noDataFn = Handlebars.compile(src, { data: false });
218-
bench.add('exec: data=false', () => noDataFn(ctx));
219-
})
216+
await runSection(
217+
'COMPILE OPTIONS COMPARISON',
218+
EXEC_BENCH_CONFIG,
219+
(bench) => {
220+
const src = allTemplates['complex (if/each/helpers)'].template;
221+
const ctx = allTemplates['complex (if/each/helpers)'].context;
222+
223+
const defaultFn = Handlebars.compile(src);
224+
bench.add('exec: default options', () => defaultFn(ctx));
225+
226+
const noEscapeFn = Handlebars.compile(src, { noEscape: true });
227+
bench.add('exec: noEscape=true', () => noEscapeFn(ctx));
228+
229+
const strictFn = Handlebars.compile(src, {
230+
strict: true,
231+
assumeObjects: true,
232+
});
233+
bench.add('exec: strict + assumeObjects', () => strictFn(ctx));
234+
235+
const knownFn = Handlebars.compile(src, {
236+
knownHelpers: { if: true, each: true },
237+
knownHelpersOnly: false,
238+
});
239+
bench.add('exec: knownHelpers', () => knownFn(ctx));
240+
241+
const compatFn = Handlebars.compile(src, { compat: true });
242+
bench.add('exec: compat=true', () => compatFn(ctx));
243+
244+
const noDataFn = Handlebars.compile(src, { data: false });
245+
bench.add('exec: data=false', () => noDataFn(ctx));
246+
}
247+
)
220248
);
221249

222250
const filepath = saveMarkdownReport(allSections, {
223251
label,
224-
config: BENCH_CONFIG,
252+
compileConfig: COMPILE_BENCH_CONFIG,
253+
execConfig: EXEC_BENCH_CONFIG,
225254
date: now,
226255
});
227256
console.log(`Results saved to: ${filepath}`);
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ export function printSectionHeader(title) {
7878

7979
// ─── Markdown report ─────────────────────────────────────────────────────────
8080

81-
export function saveMarkdownReport(sections, { label, config, date }) {
81+
export function saveMarkdownReport(
82+
sections,
83+
{ label, compileConfig, execConfig, date }
84+
) {
8285
const resultsDir = join(__dirname, 'results');
8386
mkdirSync(resultsDir, { recursive: true });
8487

@@ -96,7 +99,10 @@ export function saveMarkdownReport(sections, { label, config, date }) {
9699
lines.push(`- **Node:** ${process.version}`);
97100
lines.push(`- **Platform:** ${process.platform} ${process.arch}`);
98101
lines.push(
99-
`- **Config:** warmup=${config.warmupIterations}, minIterations=${config.iterations}, time=${config.time}ms`
102+
`- **Compile config:** warmup=${compileConfig.warmupIterations}, minIterations=${compileConfig.iterations}, time=${compileConfig.time}ms`
103+
);
104+
lines.push(
105+
`- **Exec config:** warmup=${execConfig.warmupIterations}, minIterations=${execConfig.iterations}, time=${execConfig.time}ms`
100106
);
101107
lines.push('');
102108

bench/size.mjs renamed to tests/bench/size.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
55
const __dirname = dirname(fileURLToPath(import.meta.url));
66
import { gzip } from 'node:zlib';
77
import { promisify } from 'node:util';
8-
import Handlebars from '../lib/index.js';
8+
import Handlebars from '../../lib/index.js';
99
import { templates } from './templates.mjs';
1010

1111
const gzipAsync = promisify(gzip);

0 commit comments

Comments
 (0)