Skip to content

Commit 100d584

Browse files
committed
lib: avoid quadratic shift() in startup snapshot callback
1 parent d44a71a commit 100d584

2 files changed

Lines changed: 67 additions & 6 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
// Benchmarks startup time when deserialize callbacks are registered in a
4+
// startup snapshot, measuring real binary execution with --snapshot-blob.
5+
6+
const common = require('../common.js');
7+
const { spawnSync } = require('child_process');
8+
const { mkdtempSync, writeFileSync, rmSync } = require('fs');
9+
const { tmpdir } = require('os');
10+
const path = require('path');
11+
12+
const bench = common.createBenchmark(main, {
13+
count: [10, 100, 1000, 10000],
14+
n: [30],
15+
});
16+
17+
function buildSnapshot(snapshotBlob, fixtureScript, count) {
18+
writeFileSync(fixtureScript, `
19+
'use strict';
20+
const v8 = require('node:v8');
21+
for (let i = 0; i < ${count}; i++) {
22+
v8.startupSnapshot.addDeserializeCallback(() => {});
23+
}
24+
v8.startupSnapshot.setDeserializeMainFunction(() => {});
25+
`);
26+
const result = spawnSync(process.execPath, [
27+
'--snapshot-blob', snapshotBlob,
28+
'--build-snapshot', fixtureScript,
29+
]);
30+
if (result.status !== 0) {
31+
throw new Error(`Failed to build snapshot:\n${result.stderr}`);
32+
}
33+
}
34+
35+
function main({ n, count }) {
36+
const dir = mkdtempSync(path.join(tmpdir(), 'node-bench-snapshot-'));
37+
const snapshotBlob = path.join(dir, 'snapshot.blob');
38+
const fixtureScript = path.join(dir, 'build.js');
39+
40+
try {
41+
buildSnapshot(snapshotBlob, fixtureScript, count);
42+
43+
const warmup = 3;
44+
let finished = -warmup;
45+
46+
while (finished < n) {
47+
const child = spawnSync(process.execPath, ['--snapshot-blob', snapshotBlob]);
48+
if (child.status !== 0) {
49+
throw new Error(`Snapshot run failed:\n${child.stderr}`);
50+
}
51+
finished++;
52+
if (finished === 0) bench.start();
53+
if (finished === n) bench.end(n);
54+
}
55+
} finally {
56+
rmSync(dir, { recursive: true, force: true });
57+
}
58+
}

lib/internal/v8/startup_snapshot.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ function throwIfBuildingSnapshot(reason) {
3737
const deserializeCallbacks = [];
3838
let deserializeCallbackIsSet = false;
3939
function runDeserializeCallbacks() {
40-
while (deserializeCallbacks.length > 0) {
41-
const { 0: callback, 1: data } = deserializeCallbacks.shift();
40+
for (let i = 0; i < deserializeCallbacks.length; i++) {
41+
const { 0: callback, 1: data } = deserializeCallbacks[i];
4242
callback(data);
4343
}
44+
deserializeCallbacks.length = 0;
4445
}
4546

4647
function addDeserializeCallback(callback, data) {
@@ -60,14 +61,16 @@ function addDeserializeCallback(callback, data) {
6061
const serializeCallbacks = [];
6162
const afterUserSerializeCallbacks = []; // Callbacks to run after user callbacks
6263
function runSerializeCallbacks() {
63-
while (serializeCallbacks.length > 0) {
64-
const { 0: callback, 1: data } = serializeCallbacks.shift();
64+
for (let i = 0; i < serializeCallbacks.length; i++) {
65+
const { 0: callback, 1: data } = serializeCallbacks[i];
6566
callback(data);
6667
}
67-
while (afterUserSerializeCallbacks.length > 0) {
68-
const { 0: callback, 1: data } = afterUserSerializeCallbacks.shift();
68+
serializeCallbacks.length = 0;
69+
for (let i = 0; i < afterUserSerializeCallbacks.length; i++) {
70+
const { 0: callback, 1: data } = afterUserSerializeCallbacks[i];
6971
callback(data);
7072
}
73+
afterUserSerializeCallbacks.length = 0;
7174
}
7275

7376
function addSerializeCallback(callback, data) {

0 commit comments

Comments
 (0)