1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00

perf: warm expensive init code at snapshot time (#22714)

Slightly different approach to similar changes in #22386

Note that this doesn't use a warmup script -- we are actually just doing
more work at snapshot time.
This commit is contained in:
Matt Mastracci 2024-03-22 13:49:07 -07:00 committed by GitHub
parent 9c2f9f14e7
commit 08ec6e5831
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 401 additions and 360 deletions

View file

@ -16,7 +16,9 @@ function initialize(
runningOnMainThread, runningOnMainThread,
workerId, workerId,
maybeWorkerMetadata, maybeWorkerMetadata,
warmup = false,
) { ) {
if (!warmup) {
if (initialized) { if (initialized) {
throw Error("Node runtime already initialized"); throw Error("Node runtime already initialized");
} }
@ -24,18 +26,6 @@ function initialize(
if (usesLocalNodeModulesDir) { if (usesLocalNodeModulesDir) {
requireImpl.setUsesLocalNodeModulesDir(); requireImpl.setUsesLocalNodeModulesDir();
} }
const nativeModuleExports = requireImpl.nativeModuleExports;
nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer;
nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate;
nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval;
nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout;
nodeGlobals.console = nativeModuleExports["console"];
nodeGlobals.global = globalThis;
nodeGlobals.process = nativeModuleExports["process"];
nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate;
nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval;
nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout;
nodeGlobals.performance = nativeModuleExports["perf_hooks"].performance;
// FIXME(bartlomieju): not nice to depend on `Deno` namespace here // FIXME(bartlomieju): not nice to depend on `Deno` namespace here
// but it's the only way to get `args` and `version` and this point. // but it's the only way to get `args` and `version` and this point.
@ -48,6 +38,10 @@ function initialize(
internals.__setupChildProcessIpcChannel(); internals.__setupChildProcessIpcChannel();
// `Deno[Deno.internal].requireImpl` will be unreachable after this line. // `Deno[Deno.internal].requireImpl` will be unreachable after this line.
delete internals.requireImpl; delete internals.requireImpl;
} else {
// Warm up the process module
internals.__bootstrapNodeProcess(undefined, undefined, undefined, true);
}
} }
function loadCjsModule(moduleName, isMain, inspectBrk) { function loadCjsModule(moduleName, isMain, inspectBrk) {
@ -63,3 +57,16 @@ internals.node = {
initialize, initialize,
loadCjsModule, loadCjsModule,
}; };
const nativeModuleExports = requireImpl.nativeModuleExports;
nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer;
nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate;
nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval;
nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout;
nodeGlobals.console = nativeModuleExports["console"];
nodeGlobals.global = globalThis;
nodeGlobals.process = nativeModuleExports["process"];
nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate;
nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval;
nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout;
nodeGlobals.performance = nativeModuleExports["perf_hooks"].performance;

View file

@ -16,7 +16,7 @@ import * as io from "ext:deno_io/12_io.js";
import { guessHandleType } from "ext:deno_node/internal_binding/util.ts"; import { guessHandleType } from "ext:deno_node/internal_binding/util.ts";
// https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41 // https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41
export function createWritableStdioStream(writer, name) { export function createWritableStdioStream(writer, name, warmup = false) {
const stream = new Writable({ const stream = new Writable({
emitClose: false, emitClose: false,
write(buf, enc, cb) { write(buf, enc, cb) {
@ -73,7 +73,9 @@ export function createWritableStdioStream(writer, name) {
}, },
}); });
if (writer?.isTerminal()) { // If we're warming up, create a stdout/stderr stream that assumes a terminal (the most likely case).
// If we're wrong at boot time, we'll recreate it.
if (warmup || writer?.isTerminal()) {
// These belong on tty.WriteStream(), but the TTY streams currently have // These belong on tty.WriteStream(), but the TTY streams currently have
// following problems: // following problems:
// 1. Using them here introduces a circular dependency. // 1. Using them here introduces a circular dependency.
@ -123,10 +125,11 @@ export function setReadStream(s) {
/** https://nodejs.org/api/process.html#process_process_stdin */ /** https://nodejs.org/api/process.html#process_process_stdin */
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L189 // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L189
/** Create process.stdin */ /** Create process.stdin */
export const initStdin = () => { export const initStdin = (warmup = false) => {
const fd = io.stdin ? io.STDIN_RID : undefined; const fd = io.stdin ? io.STDIN_RID : undefined;
let stdin; let stdin;
const stdinType = _guessStdinType(fd); // Warmup assumes a TTY for all stdio
const stdinType = warmup ? "TTY" : _guessStdinType(fd);
switch (stdinType) { switch (stdinType) {
case "FILE": { case "FILE": {
@ -142,6 +145,11 @@ export const initStdin = () => {
break; break;
} }
case "TTY": { case "TTY": {
// If it's a TTY, we know that the stdin we created during warmup is the correct one and
// just return null to re-use it.
if (!warmup) {
return null;
}
stdin = new readStream(fd); stdin = new readStream(fd);
break; break;
} }

View file

@ -68,7 +68,7 @@ const notImplementedEvents = [
"worker", "worker",
]; ];
export const argv: string[] = []; export const argv: string[] = ["", ""];
let globalProcessExitCode: number | undefined = undefined; let globalProcessExitCode: number | undefined = undefined;
/** https://nodejs.org/api/process.html#process_process_exit_code */ /** https://nodejs.org/api/process.html#process_process_exit_code */
@ -868,27 +868,9 @@ function synchronizeListeners() {
} }
} }
// Should be called only once, in `runtime/js/99_main.js` when the runtime is // Overwrites the 1st and 2nd items with getters.
// bootstrapped. Object.defineProperty(argv, "0", { get: () => argv0 });
internals.__bootstrapNodeProcess = function ( Object.defineProperty(argv, "1", {
argv0Val: string | undefined,
args: string[],
denoVersions: Record<string, string>,
) {
// Overwrites the 1st item with getter.
if (typeof argv0Val === "string") {
argv0 = argv0Val;
Object.defineProperty(argv, "0", {
get: () => {
return argv0Val;
},
});
} else {
Object.defineProperty(argv, "0", { get: () => argv0 });
}
// Overwrites the 2st item with getter.
Object.defineProperty(argv, "1", {
get: () => { get: () => {
if (Deno.mainModule?.startsWith("file:")) { if (Deno.mainModule?.startsWith("file:")) {
return pathFromURL(new URL(Deno.mainModule)); return pathFromURL(new URL(Deno.mainModule));
@ -896,7 +878,19 @@ internals.__bootstrapNodeProcess = function (
return join(Deno.cwd(), "$deno$node.js"); return join(Deno.cwd(), "$deno$node.js");
} }
}, },
}); });
// Should be called only once, in `runtime/js/99_main.js` when the runtime is
// bootstrapped.
internals.__bootstrapNodeProcess = function (
argv0Val: string | undefined,
args: string[],
denoVersions: Record<string, string>,
warmup = false,
) {
if (!warmup) {
argv0 = argv0Val || "";
// Manually concatenate these arrays to avoid triggering the getter
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
argv[i + 2] = args[i]; argv[i + 2] = args[i];
} }
@ -909,18 +903,28 @@ internals.__bootstrapNodeProcess = function (
core.setMacrotaskCallback(runNextTicks); core.setMacrotaskCallback(runNextTicks);
enableNextTick(); enableNextTick();
stdin = process.stdin = initStdin(); // Replace stdin if it is not a terminal
const newStdin = initStdin();
if (newStdin) {
stdin = process.stdin = newStdin;
}
// Replace stdout/stderr if they are not terminals
if (!io.stdout.isTerminal()) {
/** https://nodejs.org/api/process.html#process_process_stdout */ /** https://nodejs.org/api/process.html#process_process_stdout */
stdout = process.stdout = createWritableStdioStream( stdout = process.stdout = createWritableStdioStream(
io.stdout, io.stdout,
"stdout", "stdout",
); );
}
if (!io.stderr.isTerminal()) {
/** https://nodejs.org/api/process.html#process_process_stderr */ /** https://nodejs.org/api/process.html#process_process_stderr */
stderr = process.stderr = createWritableStdioStream( stderr = process.stderr = createWritableStdioStream(
io.stderr, io.stderr,
"stderr", "stderr",
); );
}
process.setStartTime(Date.now()); process.setStartTime(Date.now());
@ -931,6 +935,24 @@ internals.__bootstrapNodeProcess = function (
// @ts-ignore Remove setStartTime and #startTime is not modifiable // @ts-ignore Remove setStartTime and #startTime is not modifiable
delete process.setStartTime; delete process.setStartTime;
delete internals.__bootstrapNodeProcess; delete internals.__bootstrapNodeProcess;
} else {
// Warmup, assuming stdin/stdout/stderr are all terminals
stdin = process.stdin = initStdin(true);
/** https://nodejs.org/api/process.html#process_process_stdout */
stdout = process.stdout = createWritableStdioStream(
io.stdout,
"stdout",
true,
);
/** https://nodejs.org/api/process.html#process_process_stderr */
stderr = process.stderr = createWritableStdioStream(
io.stderr,
"stderr",
true,
);
}
}; };
export default process; export default process;

View file

@ -628,6 +628,28 @@ const finalDenoNs = {
bench: () => {}, bench: () => {},
}; };
ObjectDefineProperties(finalDenoNs, {
pid: core.propGetterOnly(opPid),
// `ppid` should not be memoized.
// https://github.com/denoland/deno/issues/23004
ppid: core.propGetterOnly(() => op_ppid()),
noColor: core.propGetterOnly(() => op_bootstrap_no_color()),
args: core.propGetterOnly(opArgs),
mainModule: core.propGetterOnly(() => op_main_module()),
// TODO(kt3k): Remove this export at v2
// See https://github.com/denoland/deno/issues/9294
customInspect: {
get() {
warnOnDeprecatedApi(
"Deno.customInspect",
new Error().stack,
'Use `Symbol.for("Deno.customInspect")` instead.',
);
return customInspect;
},
},
});
const { const {
denoVersion, denoVersion,
tsVersion, tsVersion,
@ -635,11 +657,11 @@ const {
target, target,
} = op_snapshot_options(); } = op_snapshot_options();
function bootstrapMainRuntime(runtimeOptions) { function bootstrapMainRuntime(runtimeOptions, warmup = false) {
if (!warmup) {
if (hasBootstrapped) { if (hasBootstrapped) {
throw new Error("Worker runtime already bootstrapped"); throw new Error("Worker runtime already bootstrapped");
} }
const nodeBootstrap = globalThis.nodeBootstrap;
const { const {
0: location_, 0: location_,
@ -663,7 +685,6 @@ function bootstrapMainRuntime(runtimeOptions) {
// Remove bootstrapping data from the global scope // Remove bootstrapping data from the global scope
delete globalThis.__bootstrap; delete globalThis.__bootstrap;
delete globalThis.bootstrap; delete globalThis.bootstrap;
delete globalThis.nodeBootstrap;
hasBootstrapped = true; hasBootstrapped = true;
// If the `--location` flag isn't set, make `globalThis.location` `undefined` and // If the `--location` flag isn't set, make `globalThis.location` `undefined` and
@ -697,14 +718,10 @@ function bootstrapMainRuntime(runtimeOptions) {
core.wrapConsole(consoleFromDeno, core.v8Console); core.wrapConsole(consoleFromDeno, core.v8Console);
} }
event.setEventTargetData(globalThis);
event.saveGlobalThisReference(globalThis);
event.defineEventHandler(globalThis, "error"); event.defineEventHandler(globalThis, "error");
event.defineEventHandler(globalThis, "load"); event.defineEventHandler(globalThis, "load");
event.defineEventHandler(globalThis, "beforeunload"); event.defineEventHandler(globalThis, "beforeunload");
event.defineEventHandler(globalThis, "unload"); event.defineEventHandler(globalThis, "unload");
event.defineEventHandler(globalThis, "unhandledrejection");
runtimeStart( runtimeStart(
denoVersion, denoVersion,
@ -713,28 +730,6 @@ function bootstrapMainRuntime(runtimeOptions) {
target, target,
); );
ObjectDefineProperties(finalDenoNs, {
pid: core.propGetterOnly(opPid),
// `ppid` should not be memoized.
// https://github.com/denoland/deno/issues/23004
ppid: core.propGetterOnly(() => op_ppid()),
noColor: core.propGetterOnly(() => op_bootstrap_no_color()),
args: core.propGetterOnly(opArgs),
mainModule: core.propGetterOnly(() => op_main_module()),
// TODO(kt3k): Remove this export at v2
// See https://github.com/denoland/deno/issues/9294
customInspect: {
get() {
warnOnDeprecatedApi(
"Deno.customInspect",
new Error().stack,
'Use `Symbol.for("Deno.customInspect")` instead.',
);
return customInspect;
},
},
});
// TODO(bartlomieju): deprecate --unstable // TODO(bartlomieju): deprecate --unstable
if (unstableFlag) { if (unstableFlag) {
ObjectAssign(finalDenoNs, denoNsUnstable); ObjectAssign(finalDenoNs, denoNsUnstable);
@ -781,10 +776,12 @@ function bootstrapMainRuntime(runtimeOptions) {
if (nodeBootstrap) { if (nodeBootstrap) {
nodeBootstrap(hasNodeModulesDir, argv0, /* runningOnMainThread */ true); nodeBootstrap(hasNodeModulesDir, argv0, /* runningOnMainThread */ true);
} }
if (future) { if (future) {
delete globalThis.window; delete globalThis.window;
} }
} else {
// Warmup
}
} }
function bootstrapWorkerRuntime( function bootstrapWorkerRuntime(
@ -793,13 +790,13 @@ function bootstrapWorkerRuntime(
internalName, internalName,
workerId, workerId,
maybeWorkerMetadata, maybeWorkerMetadata,
warmup = false,
) { ) {
if (!warmup) {
if (hasBootstrapped) { if (hasBootstrapped) {
throw new Error("Worker runtime already bootstrapped"); throw new Error("Worker runtime already bootstrapped");
} }
const nodeBootstrap = globalThis.nodeBootstrap;
const { const {
0: location_, 0: location_,
1: unstableFlag, 1: unstableFlag,
@ -817,12 +814,9 @@ function bootstrapWorkerRuntime(
performance.setTimeOrigin(DateNow()); performance.setTimeOrigin(DateNow());
globalThis_ = globalThis; globalThis_ = globalThis;
removeImportedOps();
// Remove bootstrapping data from the global scope // Remove bootstrapping data from the global scope
delete globalThis.__bootstrap; delete globalThis.__bootstrap;
delete globalThis.bootstrap; delete globalThis.bootstrap;
delete globalThis.nodeBootstrap;
hasBootstrapped = true; hasBootstrapped = true;
exposeUnstableFeaturesForWindowOrWorkerGlobalScope({ exposeUnstableFeaturesForWindowOrWorkerGlobalScope({
@ -848,12 +842,8 @@ function bootstrapWorkerRuntime(
const consoleFromDeno = globalThis.console; const consoleFromDeno = globalThis.console;
core.wrapConsole(consoleFromDeno, core.v8Console); core.wrapConsole(consoleFromDeno, core.v8Console);
event.setEventTargetData(globalThis);
event.saveGlobalThisReference(globalThis);
event.defineEventHandler(self, "message"); event.defineEventHandler(self, "message");
event.defineEventHandler(self, "error", undefined, true); event.defineEventHandler(self, "error", undefined, true);
event.defineEventHandler(self, "unhandledrejection");
// `Deno.exit()` is an alias to `self.close()`. Setting and exit // `Deno.exit()` is an alias to `self.close()`. Setting and exit
// code using an op in worker context is a no-op. // code using an op in worker context is a no-op.
@ -884,6 +874,9 @@ function bootstrapWorkerRuntime(
} }
} }
// Not available in workers
delete finalDenoNs.mainModule;
if (!ArrayPrototypeIncludes(unstableFeatures, unstableIds.unsafeProto)) { if (!ArrayPrototypeIncludes(unstableFeatures, unstableIds.unsafeProto)) {
// Removes the `__proto__` for security reasons. // Removes the `__proto__` for security reasons.
// https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ // https://tc39.es/ecma262/#sec-get-object.prototype.__proto__
@ -896,25 +889,8 @@ function bootstrapWorkerRuntime(
delete globalThis.Date.prototype.toTemporalInstant; delete globalThis.Date.prototype.toTemporalInstant;
} }
ObjectDefineProperties(finalDenoNs, { // Setup `Deno` global - we're actually overriding already existing global
pid: core.propGetterOnly(opPid), // `Deno` with `Deno` namespace from "./deno.ts".
noColor: core.propGetterOnly(() => op_bootstrap_no_color()),
args: core.propGetterOnly(opArgs),
// TODO(kt3k): Remove this export at v2
// See https://github.com/denoland/deno/issues/9294
customInspect: {
get() {
warnOnDeprecatedApi(
"Deno.customInspect",
new Error().stack,
'Use `Symbol.for("Deno.customInspect")` instead.',
);
return customInspect;
},
},
});
// Setup `Deno` global - we're actually overriding already
// existing global `Deno` with `Deno` namespace from "./deno.ts".
ObjectDefineProperty(globalThis, "Deno", core.propReadOnly(finalDenoNs)); ObjectDefineProperty(globalThis, "Deno", core.propReadOnly(finalDenoNs));
const workerMetadata = maybeWorkerMetadata const workerMetadata = maybeWorkerMetadata
@ -930,9 +906,37 @@ function bootstrapWorkerRuntime(
workerMetadata, workerMetadata,
); );
} }
} else {
// Warmup
return;
}
} }
const nodeBootstrap = globalThis.nodeBootstrap;
delete globalThis.nodeBootstrap;
globalThis.bootstrap = { globalThis.bootstrap = {
mainRuntime: bootstrapMainRuntime, mainRuntime: bootstrapMainRuntime,
workerRuntime: bootstrapWorkerRuntime, workerRuntime: bootstrapWorkerRuntime,
}; };
event.setEventTargetData(globalThis);
event.saveGlobalThisReference(globalThis);
event.defineEventHandler(globalThis, "unhandledrejection");
// Nothing listens to this, but it warms up the code paths for event dispatch
(new event.EventTarget()).dispatchEvent(new Event("warmup"));
removeImportedOps();
// Run the warmup path through node and runtime/worker bootstrap functions
bootstrapMainRuntime(undefined, true);
bootstrapWorkerRuntime(
undefined,
undefined,
undefined,
undefined,
undefined,
true,
);
nodeBootstrap(undefined, undefined, undefined, undefined, undefined, true);