Skip to content

Commit 58d58a4

Browse files
build,win: add PGO workload scripts
Signed-off-by: StefanStojanovic <stefan.stojanovic@janeasystems.com> PR-URL: #63696 Refs: #61964 Reviewed-By: Richard Lau <richard.lau@ibm.com>
1 parent ea4b37f commit 58d58a4

14 files changed

Lines changed: 5643 additions & 0 deletions

β€Žpgo.ps1β€Ž

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# PGO (Profile-Guided Optimization) training script for Node.js (Clang / LLVM)
2+
#
3+
# Runs PGO training workloads against an instrumented Node.js binary
4+
# (Release\node.exe) and merges the resulting .profraw files into
5+
# node.profdata for use with -fprofile-use.
6+
#
7+
# Usage (from a VS Developer Command Prompt):
8+
# .\pgo.ps1 # Run workloads (15s each) and merge
9+
# .\pgo.ps1 -Duration 30 # Run workloads (30s each) and merge
10+
#
11+
# Prerequisites:
12+
# - Release\node.exe must be an instrumented build (built with pgo-generate)
13+
# - llvm-profdata must be available (shipped with VS LLVM toolset)
14+
#
15+
# Output:
16+
# - node.profdata in the repo root (ready for vcbuild.bat pgo-use)
17+
18+
param(
19+
[int]$Duration = 15
20+
)
21+
22+
Set-StrictMode -Version Latest
23+
$ErrorActionPreference = 'Stop'
24+
25+
# ---------------------------------------------------------------------------
26+
# Locate llvm-profdata shipped with Visual Studio's LLVM toolset
27+
# ---------------------------------------------------------------------------
28+
29+
function Find-LlvmProfdata {
30+
# vcbuild.bat uses %VCINSTALLDIR%\Tools\Llvm\x64\bin for clang.exe - same spot for profdata
31+
$vcInstallDir = $env:VCINSTALLDIR
32+
33+
if ($vcInstallDir) {
34+
$candidate = Join-Path $vcInstallDir "Tools\Llvm\x64\bin\llvm-profdata.exe"
35+
if (Test-Path $candidate) {
36+
return $candidate
37+
}
38+
}
39+
40+
# Fallback: try VS 2022 / 2026 default install locations
41+
$vsPaths = @(
42+
"${env:ProgramFiles}\Microsoft Visual Studio\2026\Enterprise\VC\Tools\Llvm\x64\bin",
43+
"${env:ProgramFiles}\Microsoft Visual Studio\2026\Community\VC\Tools\Llvm\x64\bin",
44+
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\x64\bin",
45+
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin"
46+
)
47+
foreach ($dir in $vsPaths) {
48+
$candidate = Join-Path $dir "llvm-profdata.exe"
49+
if (Test-Path $candidate) {
50+
return $candidate
51+
}
52+
}
53+
54+
# Last resort: PATH
55+
$fromPath = Get-Command llvm-profdata -ErrorAction SilentlyContinue
56+
if ($fromPath) {
57+
return $fromPath.Source
58+
}
59+
60+
return $null
61+
}
62+
63+
# ---------------------------------------------------------------------------
64+
# Validate prerequisites
65+
# ---------------------------------------------------------------------------
66+
67+
$instrumentedNode = Join-Path $PSScriptRoot "Release\node.exe"
68+
if (-not (Test-Path $instrumentedNode)) {
69+
Write-Error "Instrumented binary not found: $instrumentedNode`nBuild with: vcbuild.bat pgo-generate"
70+
exit 1
71+
}
72+
73+
$pgoRunAll = Join-Path $PSScriptRoot "tools\pgo\pgo-run-all.js"
74+
if (-not (Test-Path $pgoRunAll)) {
75+
Write-Error "PGO training script not found: $pgoRunAll"
76+
exit 1
77+
}
78+
79+
$llvmProfdata = Find-LlvmProfdata
80+
if (-not $llvmProfdata) {
81+
Write-Error "llvm-profdata not found. Install the LLVM toolset via Visual Studio Installer."
82+
exit 1
83+
}
84+
85+
# ---------------------------------------------------------------------------
86+
# STEP 1 – Run workloads with the instrumented binary to collect profiles
87+
# ---------------------------------------------------------------------------
88+
89+
Write-Host "`n=== STEP 1: Collect PGO profiles ===" -ForegroundColor Cyan
90+
91+
# Directory that will receive .profraw files from the instrumented binary.
92+
# %p (PID) and %m (module hash) keep concurrent/fork'd processes from colliding.
93+
$profileDir = Join-Path $PSScriptRoot "pgo-profiles"
94+
95+
if (Test-Path $profileDir) {
96+
Remove-Item -Recurse -Force $profileDir
97+
}
98+
New-Item -ItemType Directory -Path $profileDir | Out-Null
99+
100+
$env:LLVM_PROFILE_FILE = Join-Path $profileDir "node-%p-%m.profraw"
101+
102+
Write-Host "Instrumented node : $instrumentedNode"
103+
Write-Host "Profile output : $($env:LLVM_PROFILE_FILE)"
104+
Write-Host "Duration per script: ${Duration}s"
105+
Write-Host ""
106+
107+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
108+
$proc = Start-Process `
109+
-FilePath $instrumentedNode `
110+
-ArgumentList "`"$pgoRunAll`" --verbose --duration=$Duration" `
111+
-Wait -PassThru -NoNewWindow
112+
$sw.Stop()
113+
Write-Host ("PGO training completed in {0}m {1}s (exit code: {2})" -f `
114+
$sw.Elapsed.Minutes, $sw.Elapsed.Seconds, $proc.ExitCode)
115+
if ($proc.ExitCode -ne 0) {
116+
Write-Warning "PGO training exited with code $($proc.ExitCode) - continuing with merge"
117+
}
118+
119+
# Remove the env var so subsequent builds are not affected
120+
Remove-Item Env:\LLVM_PROFILE_FILE -ErrorAction SilentlyContinue
121+
122+
# ---------------------------------------------------------------------------
123+
# STEP 2 – Merge .profraw files -> node.profdata
124+
# ---------------------------------------------------------------------------
125+
126+
Write-Host "`n=== STEP 2: Merge profile data ===" -ForegroundColor Cyan
127+
128+
Write-Host "Using llvm-profdata: $llvmProfdata"
129+
130+
$profrawFiles = Get-ChildItem -Path $profileDir -Filter "*.profraw" -ErrorAction SilentlyContinue
131+
if ($profrawFiles.Count -eq 0) {
132+
Write-Error "No .profraw files found in '$profileDir'. The instrumented binary may not have generated profile data."
133+
exit 1
134+
}
135+
136+
$totalSize = ($profrawFiles | Measure-Object -Property Length -Sum).Sum
137+
$totalSizeMB = [math]::Round($totalSize / 1MB, 1)
138+
Write-Host "Found $($profrawFiles.Count) .profraw file(s), ${totalSizeMB} MB total"
139+
140+
$profdata = Join-Path $PSScriptRoot "node.profdata"
141+
$mergeArgs = @("merge", "--output=$profdata") + ($profrawFiles | Select-Object -ExpandProperty FullName)
142+
143+
$mergeStopwatch = [System.Diagnostics.Stopwatch]::StartNew()
144+
& $llvmProfdata @mergeArgs
145+
$mergeExitCode = $LASTEXITCODE
146+
$mergeStopwatch.Stop()
147+
148+
if ($mergeExitCode -ne 0) {
149+
Write-Error "llvm-profdata merge failed (exit code $mergeExitCode)"
150+
exit $mergeExitCode
151+
}
152+
153+
$profdataSize = [math]::Round((Get-Item $profdata).Length / 1MB, 1)
154+
Write-Host "Merge completed in $([math]::Round($mergeStopwatch.Elapsed.TotalSeconds, 1))s"
155+
156+
# Clean up .profraw files now that they've been merged
157+
Remove-Item -Recurse -Force $profileDir
158+
Write-Host "Removed $($profrawFiles.Count) .profraw file(s) (${totalSizeMB} MB reclaimed)"
159+
160+
Write-Host "`n=== PGO training complete ===" -ForegroundColor Green
161+
Write-Host " Profile data: $profdata (${profdataSize} MB)"
162+
Write-Host " Next step: vcbuild.bat pgo-use"

β€Žtools/pgo/README.mdβ€Ž

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Node.js PGO Training Scripts
2+
3+
Training workloads for Profile-Guided Optimization (PGO) builds using
4+
Clang/LLVM (including Clang-CL on Windows).
5+
6+
## What is PGO?
7+
8+
PGO uses runtime profile data to guide compiler optimizations (inlining,
9+
branch prediction, code layout), typically improving throughput by 5-20%.
10+
11+
The process has three phases:
12+
13+
1. **Instrument** β€” Build with `-fprofile-generate` (produces `.profraw` files)
14+
2. **Train** β€” Run representative workloads to collect profile data
15+
3. **Optimize** β€” Merge `.profraw` β†’ `node.profdata` via `llvm-profdata`,
16+
then rebuild with `-fprofile-use`
17+
18+
## Quick Start
19+
20+
From a VS Developer Command Prompt:
21+
22+
```powershell
23+
# Step 1: Build the instrumented binary
24+
vcbuild.bat pgo-generate
25+
26+
# Step 2: Run workloads and merge profile data
27+
.\pgo.ps1
28+
29+
# Step 3: Build the optimized binary
30+
vcbuild.bat pgo-use
31+
```
32+
33+
`pgo.ps1` expects the instrumented binary at `Release\node.exe` (produced by
34+
step 1) and writes `node.profdata` to the repo root (consumed by step 3).
35+
36+
```powershell
37+
# Optionally set a longer training duration (default: 15s per script)
38+
.\pgo.ps1 -Duration 30
39+
```
40+
41+
## Training Scripts
42+
43+
All scripts use only Node.js built-in modules (no npm dependencies).
44+
Each script is run as a separate process via `fork()`, producing its own
45+
`.profraw` file.
46+
47+
| Script | What it exercises |
48+
| ------------------------ | ------------------------------------------------------------- |
49+
| `pgo-http-server.js` | llhttp parser, TCP stack, header serialization, JSON, routing |
50+
| `pgo-json.js` | V8 JSON parser/serializer, string allocation, GC pressure |
51+
| `pgo-crypto.js` | OpenSSL (hashing, HMAC, AES, RSA, ECDSA, random, KDF) |
52+
| `pgo-streams-buffers.js` | Buffer C++ impl, stream state machine, back-pressure |
53+
| `pgo-fs.js` | libuv fs operations, thread pool, path module |
54+
| `pgo-async-patterns.js` | V8 Promises, microtask queue, EventEmitter, timers |
55+
| `pgo-url-string.js` | Ada URL parser, V8 string internals, regex JIT |
56+
| `pgo-compression.js` | zlib, brotli C libraries, streaming compression |
57+
| `pgo-net.js` | libuv TCP/pipe handles, c-ares DNS resolver |
58+
| `pgo-module-loading.js` | Module resolver, V8 script compilation, vm module |
59+
| `pgo-child-workers.js` | Worker thread messaging, SharedArrayBuffer, inline eval |
60+
61+
### Running the Orchestrator Directly
62+
63+
The orchestrator can also be invoked directly (e.g. for testing individual
64+
workloads). When used with `pgo.ps1`, this is handled automatically.
65+
66+
```bash
67+
# Run all scripts
68+
node tools/pgo/pgo-run-all.js --duration=15 --verbose
69+
70+
# Run specific scripts
71+
node tools/pgo/pgo-run-all.js --scripts=http-server,json,crypto --duration=30
72+
73+
# Show help
74+
node tools/pgo/pgo-run-all.js --help
75+
```
76+
77+
Each script reads the `PGO_TRAINING_DURATION` environment variable (in
78+
milliseconds) to determine how long to run. The orchestrator sets this
79+
automatically from the `--duration` flag (in seconds).
80+
81+
## Files
82+
83+
```
84+
tools/pgo/
85+
β”œβ”€β”€ pgo-run-all.js # Training orchestrator
86+
β”œβ”€β”€ pgo-http-server.js # HTTP server + client workload
87+
β”œβ”€β”€ pgo-json.js # JSON parse/stringify workload
88+
β”œβ”€β”€ pgo-crypto.js # Crypto operations workload
89+
β”œβ”€β”€ pgo-streams-buffers.js # Streams and Buffer workload
90+
β”œβ”€β”€ pgo-fs.js # File system operations workload
91+
β”œβ”€β”€ pgo-async-patterns.js # Promise/async, EventEmitter, timers workload
92+
β”œβ”€β”€ pgo-url-string.js # URL parsing, string ops, regex workload
93+
β”œβ”€β”€ pgo-compression.js # Gzip/brotli/deflate compression workload
94+
β”œβ”€β”€ pgo-net.js # TCP networking and DNS workload
95+
β”œβ”€β”€ pgo-module-loading.js # Module require/import, VM compilation workload
96+
β”œβ”€β”€ pgo-child-workers.js # Worker threads workload
97+
└── README.md # This file
98+
```

0 commit comments

Comments
Β (0)