mirror of
https://github.com/electron/electron.git
synced 2026-10-28 19:54:46 +08:00
main
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a77b899ec9 |
perf: boot every Node.js-hosting process from the Node snapshot / code cache (#52873)
Since #51703 the browser process boots from the embedded Node.js startup snapshot on native builds, but every other process that hosts Node.js -- renderers with Node integration or an unsandboxed preload, Node-enabled web workers, the utility-process node service, and ELECTRON_RUN_AS_NODE / child_process.fork() children -- still ran the full bootstrap, compiling the ~120-140 builtins it loads from source on every start (and then serializing a fresh code cache for each of them that nothing consumed). Three pieces: 1. Build-time code cache for Node's own builtins in every process flavor. The electron_natives_codecache tool now also emits a V8 code cache for Node's lib/ builtins (all but internal/deps/*) for the flavors whose processes bootstrap Node from scratch, compiled against that flavor's isolate and flag set like the electron/js2c/* bundles already were. The cache has to be in place before node::NewContext and node::CreateEnvironment run the bootstrap, i.e. before Electron can reach env->builtin_loader(), so it is installed as a process-wide default every BuiltinLoader is seeded with (new BuiltinLoader::SetProcessDefaultCodeCache() hook in the js2c code-cache node patch), from JavascriptEnvironment / NodeBindings::Initialize / the web-worker path. GN decides per flavor whether Node's builtins are embedded (always for the renderer flavor; utility/browser only on builds without a Node snapshot), so native builds pay for one copy. 2. ELECTRON_RUN_AS_NODE children and the utility-process node service create their isolate from the embedded Node snapshot too. Each has exactly one JavascriptEnvironment isolate (worker threads inherit the snapshot through IsolateData), so JavascriptEnvironment hands them the snapshot like the browser process, CreateIsolateData gets its EmbedderSnapshotData, CreateEnvironment an empty context, the deserialized context is entered, and the duplicate per-isolate message listener the snapshot path already installed is skipped. Cross builds and custom-V8-snapshot setups keep the from-scratch path. The js2c cache flavor now follows what the isolate was actually created from (ELECTRON_RUN_AS_NODE children were previously fed the browser flavor keyed to the Node-snapshot isolate, so even their node_init cache was silently rejected). 3. A node_platform fix this exposed: WorkerThreadsTaskRunner's DelayedTaskScheduler drains its queue through a TaskQueue, which is a std::priority_queue treating plain v8::Tasks as equal, so a StopTask can run before an earlier-queued ScheduleTask; that then arms a timer on the stopped loop and NodePlatform::Shutdown() waits it out -- observed as ~10 % of forked children stalling exactly 8 s (V8's memory-reducer delay) at exit in release builds. New patch drops ScheduleTasks that arrive after StopTask; upstreamable. The DCHECK-only cache-status record now covers all builtins, and the js2c code-cache spec asserts that browser / renderer / utility / run-as-node compile no Node builtin without consuming a cache entry, once as built and once from a copy of the app with the LoadBrowserProcessSpecificV8Snapshot fuse enabled. Whether a process may use the Node snapshot is decided by comparing the blob it loaded with the built-in one alone, so the fuse only affects the process whose blob it actually swaps. spec/node-spec.ts, utility-process, net, modules, asar, esm, context-bridge, ipc-renderer specs pass locally. Linux x64 release build (official, ThinLTO, PGO), interleaved A/B vs main, n=40 fresh processes per cell: ELECTRON_RUN_AS_NODE=1 electron -e 0 82.5 -> 37.0 ms child_process.fork(noop) -> 'exit' 89.4 -> 41.1 ms utilityProcess.fork -> first message 86.1 -> 39.7 ms nodeIntegration renderer, timeOrigin -> first preload statement 90.9 -> 64.7 ms stripped binary +2.0 MB |
||
|
|
d8571176d3 |
perf: build-time V8 code cache for the electron/js2c framework bundles (#51697)
* build: dedupe get-intrinsic to shrink the sandboxed renderer bundle The hoisted get-intrinsic resolved to 1.2.1 (satisfying the older ^1.0.2..^1.2.0 consumers), so side-channel-weakmap, side-channel-map, call-bound and es-set-tostringtag — which need ^1.2.5/^1.3.0 — each pulled a nested get-intrinsic@1.3.0 copy. webpack bundled three of those nested copies into sandbox_bundle.js (~16.5 KB each). 1.3.0 satisfies every range in the tree (all ^1.x), so pin it via resolutions to collapse to a single copy. Drops sandbox_bundle.js from 364,762 to 323,116 bytes (-41,646, -11.4%) — that bundle is parsed/preparsed in every sandboxed renderer before the HTML parser, so this is a direct per-renderer startup win on top of the IPC and code-cache changes. * perf: build-time V8 code cache for the electron/js2c framework bundles Generate a V8 code cache for Electron's embedded electron/js2c/* bundles at build time and embed it in the binary, so each process deserializes its framework bundle (kConsumeCodeCache) instead of parsing and compiling it from source at startup. V8's code-cache key includes FlagList::Hash() over non-default flags and the snapshot's read-only checksum, both of which differ per process type, so one cache variant is generated per flavor and each process consumes the matching one: sandbox : sandboxed renderer, no Node env (+SAB) renderer : normal renderer, has Node env (+SAB +rehash +no-freeze) browser : main process (+rehash +no-freeze) utility : utility process (+rehash +no-freeze) worker : node/web worker (+rehash +no-freeze) All flavors create their isolate from the v8 context snapshot. The flag deltas are deterministic; --no-freeze-flags-after-init (present iff the process has a Node Environment) lets V8 recompute the flag-hash after init, so the consume-time hash of every node-env process includes --rehash-snapshot. A sandboxed renderer has no Node env (flags stay frozen) so it is a distinct flavor, discriminated at runtime by node::Environment::GetCurrent(context). The generator compiles each bundle with kEagerCompile so the cache covers the inner functions, not just the top-level wrapper -- the framework bundles run in full at startup, so this front-loads the lazy compiles to build time. Cross-arch is supported (the generator is built in v8_snapshot_toolchain so it links V8 with V8_TARGET_ARCH set to the target and reads the target's snapshot blob); cross-OS is untested and gated off. - electron_natives_codecache_main.cc: per-flavor build-time generator compiling each bundle through the same BuiltinLoader path its runtime consumer uses. - shell/common/js2c_bundle_ids.h: shared bundle ids and wrapper-function param names used by the generator and the runtime CompileAndCall call sites, so the source/params hash matches by construction. - node_util.cc feeds the standalone loader; FeedEnvironmentCodeCache feeds the per-Environment loader before LoadEnvironment so the *_init bundles are consumed. - Test-only (DCHECK builds, compiled out of production): a per-process id->accepted status map exposed via v8_util.getJs2cCodeCacheStatus, a sandboxed-preload shim, and spec/api-js2c-code-cache-spec.ts which spawns a clean app and asserts every bundle is consumed in browser/sandbox/renderer/utility. DidClearWindowObject (sandboxed renderer, pre-HTML-parser): ~9.8 ms (cache rejected/absent) -> ~6.4 ms (eager cache consumed), a ~3.4 ms / ~35% cold-start win on every launch. No V8 patch. * chore: export node patch for the js2c build-time code cache |