perf: make the Node compile cache opt-in, persist worker caches on teardown - #10742
Conversation
✅ Deploy Preview for vitest-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@AriPerkkio I should've tested more without trusting AI, you were right to question what are the actual numbers 😄 I don't think the cold start cost justifies the warm improvement here since CI will always run cold |
| // persisting). A no-op when the cache is disabled or was fully loaded | ||
| // from disk, and cheap (~tens of ms) otherwise, so every worker can | ||
| // afford it. | ||
| const persistCompileCache = () => { |
There was a problem hiding this comment.
since we kill the child, forks never actually flush the cache otherwise
There was a problem hiding this comment.
There's actually small time window where the worker can gracefully exit, before main thread kills it. But anyways, flushing the cache manually like this is good approach.
There was a problem hiding this comment.
threads workers do flush gracefully, but we for forks we kill them and they don't have enough time (or even knowledge?) to flush. in my tests they never flushed the cache
There was a problem hiding this comment.
Removing these two lines shows that forks worker does exit gracefully without the need of main thread killing it:
vitest/packages/vitest/src/node/pools/workers/forksWorker.ts
Lines 80 to 83 in 83ab9a4
cd test/unit
pnpm run test test/math.test.ts --project forks --runMaybe enabling compile cache prevents worker from exiting. 🤔
#10708 enabled Node's compile cache unconditionally. That turned out to be a bad default in two ways:
forkspool the cache was write-only anyway: SIGTERM'd workers never reach Node's exit-time flush, so worker entries (runtime, test environment, externalized deps) never reached the disk even when the directory did persist.This PR makes the cache opt-in and makes opting in actually work:
module.enableCompileCache(). SettingNODE_COMPILE_CACHE=<dir>enables the cache for the CLI (Node reads it at startup) and Vitest propagates it to every worker.module.flushCompileCache()in thestophandler, both teardown paths; default import because a named import fails to link before Node 22.10). This is what makes the cache useful forforks.v8coverage provider still strips the cache from workers (V8 serializes cached scripts without the source positions precise coverage relies on) — now covered by a test.docs/guide/improving-performance.mdas a tuning knob for environments where the directory survives between runs.40-file happy-dom
isolate: truesuite, median of 5 interleaved runs (Apple M4 ×10, Node 24):NODE_COMPILE_CACHE, first runNODE_COMPILE_CACHE, subsequent runsNODE_COMPILE_CACHE, first runNODE_COMPILE_CACHE, subsequent runsCold runs get faster by default. Opting in pays the first-run serialization once and runs ~23% faster than that first run afterwards; the benefit over not enabling the cache at all grows with the size of the natively-imported graph (heavier environments and dependencies).
The e2e test covers env propagation into workers,
NODE_DISABLE_COMPILE_CACHEprecedence, and the coverage exclusion.