Skip to content

Commit 08e2de1

Browse files
authored
Revive and improve the JS compiler brenchmarker profiler. NFC (#25711)
There is a sample output: ``` $ EMPROFILE=2 ./emcc ~/test/hello.cprofiler:INFO: start block "read_ports" profiler:INFO: block "read_ports" took 0.004 seconds profiler:INFO: start block "main" profiler:INFO: start block "parse_arguments" profiler:INFO: block "parse_arguments" took 0.000 seconds profiler:INFO: start block "check_sanity" profiler:INFO: block "check_sanity" took 0.000 seconds profiler:INFO: start block "setup" profiler:INFO: block "setup" took 0.000 seconds profiler:INFO: start block "compile inputs" profiler:INFO: start block "ensure_sysroot" profiler:INFO: block "ensure_sysroot" took 0.000 seconds profiler:INFO: block "compile inputs" took 0.067 seconds profiler:INFO: start block "linker_setup" profiler:INFO: block "linker_setup" took 0.048 seconds profiler:INFO: start block "calculate linker inputs" profiler:INFO: block "calculate linker inputs" took 0.000 seconds profiler:INFO: start block "calculate system libraries" profiler:INFO: block "calculate system libraries" took 0.001 seconds profiler:INFO: start block "JS symbol generation" profiler:INFO: block "JS symbol generation" took 0.013 seconds profiler:INFO: start block "link" profiler:INFO: block "link" took 0.079 seconds profiler:INFO: start block "post link" profiler:INFO: start block "emscript" profiler:INFO: start block "get_metadata" profiler:INFO: block "get_metadata" took 0.001 seconds profiler:INFO: start block "compile_javascript" [prof] -> overall [prof] -> startup [prof] -> loadSettingsFile [prof] <- loadSettingsFile [1.8 ms] [prof] -> loadSettingsFile [prof] <- loadSettingsFile [1.6 ms] [prof] -> read settings [prof] <- read settings [0.2 ms] [prof] -> dynamic imports [prof] <- dynamic imports [8.1 ms] [prof] <- startup [13.2 ms] [prof] -> runJSify [prof] -> load [prof] -> preprocessFiles [prof] <- preprocessFiles [65.9 ms] [prof] -> executeJS [prof] <- executeJS [31.0 ms] [prof] <- load [97.9 ms] [prof] -> finalCombiner [prof] <- finalCombiner [12.4 ms] [prof] <- runJSify [114.0 ms] [prof] <- overall [135.6 ms] profiler:INFO: block "compile_javascript" took 0.177 seconds profiler:INFO: block "emscript" took 0.188 seconds profiler:INFO: start block "binaryen" profiler:INFO: block "binaryen" took 0.000 seconds profiler:INFO: start block "final emitting" profiler:INFO: block "final emitting" took 0.001 seconds profiler:INFO: block "post link" took 0.189 seconds profiler:INFO: block "main" took 0.403 seconds ```
1 parent 74f5428 commit 08e2de1

File tree

4 files changed

+72
-26
lines changed

4 files changed

+72
-26
lines changed

src/jsifier.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
warnOnce,
4040
warningOccured,
4141
localFile,
42+
timer,
4243
} from './utility.mjs';
4344
import {LibraryManager, librarySymbols, nativeAliases} from './modules.mjs';
4445

@@ -918,7 +919,9 @@ var proxiedFunctionTable = [
918919
}),
919920
);
920921
} else {
922+
timer.start('finalCombiner')
921923
finalCombiner();
924+
timer.stop('finalCombiner')
922925
}
923926

924927
if (errorOccured()) {

src/modules.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
runInMacroContext,
2323
mergeInto,
2424
localFile,
25+
timer,
2526
} from './utility.mjs';
2627
import {preprocess, processMacros} from './parseTools.mjs';
2728

@@ -224,6 +225,7 @@ function getTempDir() {
224225
}
225226

226227
function preprocessFiles(filenames) {
228+
timer.start('preprocessFiles')
227229
const results = {};
228230
for (const filename of filenames) {
229231
debugLog(`pre-processing JS library: ${filename}`);
@@ -237,6 +239,7 @@ function preprocessFiles(filenames) {
237239
popCurrentFile();
238240
}
239241
}
242+
timer.stop('preprocessFiles')
240243
return results;
241244
}
242245

@@ -262,15 +265,22 @@ export const LibraryManager = {
262265
},
263266

264267
load() {
268+
timer.start('load')
269+
265270
assert(!this.loaded);
266271
this.loaded = true;
267272
// Save the list for has() queries later.
268273
this.libraries = calculateLibraries();
269274

270275
const preprocessed = preprocessFiles(this.libraries);
276+
277+
timer.start('executeJS')
271278
for (const [filename, contents] of Object.entries(preprocessed)) {
272279
this.executeJSLibraryFile(filename, contents);
273280
}
281+
timer.stop('executeJS')
282+
283+
timer.stop('load')
274284
},
275285

276286
executeJSLibraryFile(filename, contents) {

src/utility.mjs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -251,40 +251,61 @@ export function debugLog(...args) {
251251
if (VERBOSE) printErr(...args);
252252
}
253253

254-
export class Benchmarker {
255-
totals = {};
254+
class Profiler {
256255
ids = [];
257256
lastTime = 0;
258257

258+
constructor() {
259+
this.start('overall')
260+
this.startTime = performance.now();
261+
}
262+
263+
log(msg) {
264+
const depth = this.ids.length;
265+
const indent = ' '.repeat(depth)
266+
printErr('[prof] ' + indent + msg);
267+
}
268+
259269
start(id) {
260-
const now = Date.now();
261-
if (this.ids.length > 0) {
262-
this.totals[this.ids[this.ids.length - 1]] += now - this.lastTime;
263-
}
264-
this.lastTime = now;
265-
this.ids.push(id);
266-
this.totals[id] ||= 0;
270+
this.log(`-> ${id}`)
271+
const now = performance.now();
272+
this.ids.push([id, now]);
267273
}
268274

269275
stop(id) {
270-
const now = Date.now();
271-
assert(id === this.ids[this.ids.length - 1]);
272-
this.totals[id] += now - this.lastTime;
273-
this.lastTime = now;
274-
this.ids.pop();
276+
const [poppedId, startTime] = this.ids.pop();
277+
assert(id === poppedId);
278+
const now = performance.now();
279+
const duration = now - startTime;
280+
this.log(`<- ${id} [${duration.toFixed(1)} ms]`)
275281
}
276282

277-
print(text) {
278-
const ids = Object.keys(this.totals);
279-
if (ids.length > 0) {
280-
ids.sort((a, b) => this.totals[b] - this.totals[a]);
281-
printErr(
282-
text + ' times: \n' + ids.map((id) => id + ' : ' + this.totals[id] + ' ms').join('\n'),
283-
);
283+
terminate() {
284+
while (this.ids.length) {
285+
const lastID = this.ids[this.ids.length - 1][0];
286+
this.stop(lastID);
284287
}
288+
// const overall = performance.now() - this.startTime
289+
// printErr(`overall total: ${overall.toFixed(1)} ms`);
285290
}
286291
}
287292

293+
class NullProfiler {
294+
start(_id) {}
295+
stop(_id) {}
296+
terminate() {}
297+
}
298+
299+
// Enable JS compiler profiling if EMPROFILE is "2". This mode reports profile
300+
// data to stderr.
301+
const EMPROFILE = process.env.EMPROFILE == '2';
302+
303+
export const timer = EMPROFILE ? new Profiler() : new NullProfiler();
304+
305+
if (EMPROFILE) {
306+
process.on('exit', () => timer.terminate());
307+
}
308+
288309
/**
289310
* Context in which JS library code is evaluated. This is distinct from the
290311
* global scope of the compiler itself which avoids exposing all of the compiler
@@ -311,9 +332,11 @@ export function applySettings(obj) {
311332
}
312333

313334
export function loadSettingsFile(f) {
335+
timer.start('loadSettingsFile')
314336
const settings = {};
315337
vm.runInNewContext(readFile(f), settings, {filename: f});
316338
applySettings(settings);
339+
timer.stop('loadSettingsFile')
317340
return settings;
318341
}
319342

tools/compiler.mjs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
import assert from 'node:assert';
1111
import {parseArgs} from 'node:util';
1212
import {
13-
Benchmarker,
1413
applySettings,
1514
loadDefaultSettings,
1615
printErr,
1716
readFile,
17+
timer,
1818
} from '../src/utility.mjs';
1919

20+
timer.start('startup')
21+
2022
loadDefaultSettings();
2123

2224
const options = {
@@ -36,6 +38,8 @@ Usage: compiler.mjs <settings.json> [-o out.js] [--symbols-only]`);
3638
process.exit(0);
3739
}
3840

41+
timer.start('read settings')
42+
3943
// Load settings from JSON passed on the command line
4044
let settingsFile = positionals[0];
4145
assert(settingsFile, 'settings file not specified');
@@ -46,6 +50,8 @@ if (settingsFile == '-') {
4650
const userSettings = JSON.parse(readFile(settingsFile));
4751
applySettings(userSettings);
4852

53+
timer.stop('read settings')
54+
4955
export const symbolsOnly = values['symbols-only'];
5056

5157
// TODO(sbc): Remove EMCC_BUILD_DIR at some point. It used to be required
@@ -77,6 +83,8 @@ assert(
7783

7884
// Load compiler code
7985

86+
timer.start('dynamic imports')
87+
8088
// We can't use static import statements here because several of these
8189
// file depend on having the settings defined in the global scope (which
8290
// we do dynamically above.
@@ -87,16 +95,18 @@ if (!STRICT) {
8795
}
8896
const jsifier = await import('../src/jsifier.mjs');
8997

98+
timer.stop('dynamic imports')
99+
100+
timer.stop('startup')
101+
90102
// ===============================
91103
// Main
92104
// ===============================
93105

94-
const B = new Benchmarker();
95-
96106
try {
107+
timer.start('runJSify')
97108
await jsifier.runJSify(values.output, symbolsOnly);
98-
99-
B.print('glue');
109+
timer.stop('runJSify')
100110
} catch (err) {
101111
if (err.toString().includes('Aborting compilation due to previous errors')) {
102112
// Compiler failed on user error, don't print the stacktrace in this case.

0 commit comments

Comments
 (0)