mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
perf(build): cap Rolldown threads on large hosts; build pairing web client in the background (#33828)
Rolldown defaults to 1.5x physical cores Tokio workers plus a Rayon pool per logical CPU (24+24 here). Capping both at 8 on >8-core hosts cuts electron-vite build CPU 24% with byte-identical output. pnpm dev no longer blocks on a stale pairing web build; it stages into out/.web-next and swaps on success. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import os from 'node:os'
|
||||
|
||||
// Measured on 24 threads: Rolldown's default 24+24 workers cost ~25% more CPU than 8 for no wall-time gain.
|
||||
const maxRolldownThreads = '8'
|
||||
|
||||
/** Caps Rolldown's Tokio and Rayon pools on large hosts unless the caller already chose sizes. */
|
||||
export function withRolldownThreadLimit(env, availableParallelism = os.availableParallelism()) {
|
||||
if (availableParallelism <= Number(maxRolldownThreads)) {
|
||||
return env
|
||||
}
|
||||
return {
|
||||
...env,
|
||||
ROLLDOWN_WORKER_THREADS: env.ROLLDOWN_WORKER_THREADS ?? maxRolldownThreads,
|
||||
RAYON_NUM_THREADS: env.RAYON_NUM_THREADS ?? maxRolldownThreads
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
|
||||
|
||||
describe('withRolldownThreadLimit', () => {
|
||||
it('caps both pools on large hosts', () => {
|
||||
expect(withRolldownThreadLimit({ A: '1' }, 24)).toEqual({
|
||||
A: '1',
|
||||
ROLLDOWN_WORKER_THREADS: '8',
|
||||
RAYON_NUM_THREADS: '8'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps Rolldown defaults on small hosts and explicit sizes everywhere', () => {
|
||||
expect(withRolldownThreadLimit({}, 8)).toEqual({})
|
||||
expect(withRolldownThreadLimit({ RAYON_NUM_THREADS: '2' }, 24).RAYON_NUM_THREADS).toBe('2')
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import fs from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { appendBuildOldSpaceOption } from './node-old-space-limit.mjs'
|
||||
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
|
||||
import { RENDERER_BUILD_DIR, verifyRendererBootGraph } from './renderer-boot-graph.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
@@ -15,7 +16,7 @@ const nodeOptions = appendBuildOldSpaceOption(process.env.NODE_OPTIONS)
|
||||
const child = spawn(process.execPath, [electronViteCli, 'build', ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
...withRolldownThreadLimit(process.env),
|
||||
NODE_OPTIONS: nodeOptions
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
readlinkSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
symlinkSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { prepareDevCliTerminalWrappers } from './dev-cli-terminal-wrapper.mjs'
|
||||
@@ -36,6 +38,7 @@ import {
|
||||
isDevWebClientFresh,
|
||||
recordDevWebClientInputs
|
||||
} from './dev-web-client-freshness.mjs'
|
||||
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
|
||||
import { copyPrivateTree } from './space-sharing-copy.mjs'
|
||||
import {
|
||||
DEV_BUNDLE_ID,
|
||||
@@ -513,17 +516,53 @@ function prepareDevWebClient() {
|
||||
return
|
||||
}
|
||||
const inputs = captureDevWebClientInputs(repoRoot)
|
||||
console.error('[orca-dev] Building web client for pairing...')
|
||||
execFileSync(
|
||||
console.error('[orca-dev] Building web client for pairing in the background...')
|
||||
// Pairing reads out/web per request, so a stale bundle must not delay Electron startup.
|
||||
// Stage then swap, so an interrupted build never leaves out/web empty.
|
||||
const stagingDir = path.join(repoRoot, 'out', '.web-next')
|
||||
const build = spawn(
|
||||
process.execPath,
|
||||
[viteCli, 'build', '--config', path.join(repoRoot, 'vite.web.config.ts')],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
stdio: 'inherit',
|
||||
env: process.env
|
||||
}
|
||||
[
|
||||
viteCli,
|
||||
'build',
|
||||
'--config',
|
||||
path.join(repoRoot, 'vite.web.config.ts'),
|
||||
'--outDir',
|
||||
stagingDir,
|
||||
'--logLevel',
|
||||
'warn'
|
||||
],
|
||||
{ cwd: repoRoot, stdio: 'inherit', env: withRolldownThreadLimit(process.env) }
|
||||
)
|
||||
recordDevWebClientInputs(repoRoot, inputs)
|
||||
webClientBuild = build
|
||||
try {
|
||||
os.setPriority(build.pid, os.constants.priority.PRIORITY_BELOW_NORMAL)
|
||||
} catch {
|
||||
// Priority is best effort; the build still runs at normal priority.
|
||||
}
|
||||
build.once('error', (error) => {
|
||||
console.error(`[orca-dev] Web client build failed to start: ${error.message}`)
|
||||
})
|
||||
build.once('exit', (code, signal) => {
|
||||
webClientBuild = null
|
||||
if (code !== 0) {
|
||||
if (!isShuttingDown) {
|
||||
console.error(
|
||||
`[orca-dev] Web client build failed (${signal ?? code}); pairing serves the previous bundle`
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
try {
|
||||
const outDir = path.dirname(getDevWebClientIndexPath())
|
||||
rmSync(outDir, { recursive: true, force: true })
|
||||
renameSync(stagingDir, outDir)
|
||||
recordDevWebClientInputs(repoRoot, inputs)
|
||||
console.error('[orca-dev] Web client for pairing is ready')
|
||||
} catch (error) {
|
||||
console.error(`[orca-dev] Could not install the web client build: ${error.message}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!isHelpOrVersion && process.env.ORCA_DEV_INSTANCE_LABEL) {
|
||||
@@ -542,7 +581,6 @@ if (!isHelpOrVersion) {
|
||||
forwardedExtras = ports.forwardedExtras
|
||||
announceDevInstance({ repoRoot, profile: devProfile, ...ports })
|
||||
}
|
||||
prepareDevWebClient()
|
||||
const forwardedArgs = ['dev', ...forwardedRaw, ...forwardedExtras]
|
||||
const child = spawn(process.execPath, [electronViteCli, ...forwardedArgs], {
|
||||
stdio: 'inherit',
|
||||
@@ -551,13 +589,16 @@ const child = spawn(process.execPath, [electronViteCli, ...forwardedArgs], {
|
||||
detached: process.platform !== 'win32'
|
||||
})
|
||||
|
||||
let webClientBuild = null
|
||||
let isShuttingDown = false
|
||||
|
||||
child.once('spawn', () => {
|
||||
if (!isHelpOrVersion) {
|
||||
startDevStorageReclaim({ repoRoot })
|
||||
}
|
||||
prepareDevWebClient()
|
||||
})
|
||||
|
||||
let isShuttingDown = false
|
||||
let forcedKillTimer = null
|
||||
|
||||
function signalExitCode(signal) {
|
||||
@@ -600,6 +641,7 @@ function beginShutdown(signal) {
|
||||
}
|
||||
isShuttingDown = true
|
||||
|
||||
webClientBuild?.kill()
|
||||
terminateChild(signal)
|
||||
forcedKillTimer = setTimeout(() => {
|
||||
terminateChild('SIGKILL')
|
||||
@@ -626,6 +668,8 @@ child.on('exit', (code, signal) => {
|
||||
if (forcedKillTimer) {
|
||||
clearTimeout(forcedKillTimer)
|
||||
}
|
||||
// A half-written out/web must not outlive the session that started it.
|
||||
webClientBuild?.kill()
|
||||
|
||||
if (isShuttingDown) {
|
||||
process.exit(signalExitCode(signal ?? 'SIGINT'))
|
||||
|
||||
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { appendBuildOldSpaceOption } from './node-old-space-limit.mjs'
|
||||
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const vitePackageJson = require.resolve('vite/package.json')
|
||||
@@ -16,7 +17,7 @@ const child = spawn(
|
||||
{
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
...withRolldownThreadLimit(process.env),
|
||||
NODE_OPTIONS: nodeOptions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,8 @@ describe('run-electron-vite-dev web client prepare', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// The pairing build runs beside electron-vite instead of before it.
|
||||
await waitFor(() => existsSync(viteFile))
|
||||
expect(readFileSync(viteFile, 'utf8')).toContain('build')
|
||||
|
||||
const trackedPids = trackPidFile(pidFile)
|
||||
|
||||
Reference in New Issue
Block a user