script/pgo/README.md
This directory contains the infrastructure for generating Electron-specific PGO (profile-guided optimization) profiles. Two kinds of profile are generated:
.profdata) - guide clang's optimization of all
compiled C++ (Chromium, V8's C++, Node.js, the Electron shell)..profile) - guide mksnapshot's basic-block
ordering of the V8 builtins baked into the snapshot (property access ICs,
Array.prototype.*, promise machinery, etc.).Electron release builds use chrome_pgo_phase = 2, which applies a PGO
profile during compilation. Today that profile is the one Chromium publishes
for Chrome (downloaded by tools/update_pgo_profiles.py). That profile is
collected on Chrome's binary - every function that differs in Electron
(patched Chromium code, all of Node.js, the Electron shell, V8 built with
Node's flags) silently loses profile guidance.
Measured on Speedometer 3.1 (Linux x64), replacing Chrome's profile with an Electron-collected profile is worth roughly +9%.
The same problem applies to V8 builtins: Chrome's published builtins profiles
reject Electron's promise/async builtins (RunMicrotasks,
AsyncFunctionAwait, FulfillPromise, PromiseConstructor, ...) because
Electron's Node.js integration changes their code generation
(v8_promise_internal_field_count = 1,
v8_enable_javascript_promise_hooks = true). An Electron-generated builtins
profile covers every builtin with zero rejections.
The process mirrors Chromium's own PGO recipe
(tools/pgo/generate_profile.py), extended with Electron-specific workloads:
build/args/pgo-instrument.gn
(chrome_pgo_phase = 1).script/pgo/collect-profile.js, which:
LLVM_PROFILE_FILE set and drives
every workload via script/pgo/benchmark-app:
ipcRenderer.invoke round trips at multiple payload sizes;app.quit() so every Electron process writes its
counters (killed processes write nothing);.profraw files with llvm-profdata into a single
.profdata..profraw artifacts
(collection hosts can't always run the x64 LLVM tooling - e.g. Linux arm64
runners); the merge-profiles job then merges each platform/arch's set
into a .profdata using the in-tree llvm-profdata tooling (the merge job
restores the Linux src cache and runs Chromium's standard
tools/clang/scripts/update.py --package=coverage_tools), and the
upload-profiles job authenticates to Azure with OIDC through the
pgo-upload GitHub environment and uploads to the pgo container of the
build-tools storage account. Uploaded profiles are served at
https://dev-cdn.electronjs.org/pgo/<profile-name>.The process mirrors upstream V8's recipe (v8/tools/builtins-pgo/generate.py):
build/args/pgo-builtins-instrument.gn
(v8_enable_builtins_profiling = true). Configuration matching is
mandatory - builtin block-graph hashes depend on V8 codegen flags, and a
profile generated with the wrong configuration is rejected by mksnapshot.--turbo-profiling-output=<log> (the same
workload upstream V8 uses for its profiles).v8/tools/builtins-pgo/get_hints.py.Builtin block graphs are arch-independent, so - mirroring upstream V8, where arm64 release builds consume the x64 profile - one x64-generated profile covers Electron's x64 and arm64 builds. 32-bit targets (win-x86, linux-arm) keep using upstream's x86 profile until 32-bit generation is added.
# 1. Build the instrumented Electron (example with build-tools)
e init pgo-instrument --root=$PWD --import pgo-instrument
e build
# 2. Collect (Linux needs a display, e.g. under xvfb-run)
xvfb-run -a -s "-screen 0 1280x1024x24" \
node electron/script/pgo/collect-profile.js \
--electron out/Release/electron \
--output out/Release/electron.profdata
The collection takes roughly 30-60 minutes (instrumented builds run at about half speed).
For the V8 builtins profile:
# 1. Build instrumented d8
e init pgo-builtins --root=$PWD --import pgo-builtins-instrument
e build --target v8:d8
# 2. Run JetStream 2 under d8 and convert to block hints
git clone --depth 1 https://github.com/WebKit/JetStream.git /tmp/jetstream
cd /tmp/jetstream
<out>/d8 --no-sandbox-prohibit-insecure-mode \
--turbo-profiling-output=/tmp/v8.builtins.log cli.js
python3 <src>/v8/tools/builtins-pgo/get_hints.py \
/tmp/v8.builtins.log electron-v8-x64.profile
Point release builds at the profiles with:
# C++ binary profile
pgo_data_path = "//path/to/electron-<platform>-<arch>-....profdata"
# V8 builtins profile (must be an in-tree path for remote-exec builds)
v8_builtins_profiling_log_file = "//v8/tools/builtins-pgo/profiles/electron-v8-x64.profile"
Profiles are generated for every published Electron platform/arch - each combo's instrumented build runs its own benchmarks:
| Profile | Collection host |
|---|---|
| linux-x64 | x64 ARC runner (build container) |
| linux-arm | arm64 ARC runner (arm32v7 test container) |
| linux-arm64 | ubuntu-22.04-arm (arm64v8 test container) |
| win-x64 | windows-latest |
| win-x86 | windows-latest (WOW64) |
| win-arm64 | windows-11-arm |
| macos-x64 | macos-15-large |
| macos-arm64 | macos-15 |