Skip to content

feat(vpx): add opt-in warm-start cache prototype - #3343

Closed
fengmk2 wants to merge 2 commits into
mainfrom
feat/vpx-warm-start-cache-prototype
Closed

fengmk2 wants to merge 2 commits into
mainfrom
feat/vpx-warm-start-cache-prototype

Conversation

@fengmk2

@fengmk2 fengmk2 commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

A fresh vpx process repeats Oxc transforms and JavaScript compilation for unchanged scripts. Set VPX_CACHE=1 to enable the Oxc transform cache and the Node.js compile cache. Each invocation still executes the script. Both caches are off by default. Set VPX_DISABLE_CACHE=1 to disable both managed caches.

The transform cache groups small records from the same source directory in one file. Large records use individual files. Vite Task excludes the cache directory from task inputs and outputs. The store has no automatic cleanup.

Knip scanning Marked

We ran Knip 6.44.2 from its TypeScript source to check Marked 18.1.3 for unused production files and exports. Knip ran the analysis on every launch with its own cache off. Every mode reported the same five files. Knip source · Marked checkout

Median times cover the full command from global vpx launch through exit on an Apple M4 Max with Node.js 24.22.2. The comparison uses PR base 0bea91491 and head 2c140295c.

Cache state Before PR After, caches off After, VPX_CACHE=transform After, VPX_CACHE=1 Both caches vs. before
Warm 277.8 ms 277.6 ms 268.2 ms 249.6 ms 10.1% shorter
Cold 279.5 ms 280.0 ms 323.2 ms 419.8 ms 50.2% longer

Each mode used 120 warm processes and 18 cold processes across two runs. Cold processes started with empty vpx caches. A separate warm launch recorded 428 transform-cache hits and zero misses.

Command for the Knip scan

Run from the Knip checkout with Marked in the adjacent marked directory.

vpx packages/knip/src/cli.ts --directory ../marked --production \
  --include files,exports --reporter json --no-exit-code

Startup performance

The tables show median time in milliseconds from global vpx launch to script readiness on an Apple M4 Max with Node.js 24.22.2. Before uses the PR base 0bea91491. After uses 2c140295c with the indicated cache setting.

With both caches enabled, warm startup took 37–40% less time for the large files and about 3% more time for the 300-module graphs.

Warm startup (60 fresh processes per case)

Script Before After, caches off After, VPX_CACHE=transform After, VPX_CACHE=1 Both caches vs. before
Small script 33.01 33.23 33.08 33.29 0.8% longer
300-module ESM 70.08 70.73 69.56 72.05 2.8% longer
300-module CommonJS 70.02 70.39 69.47 72.36 3.3% longer
1.8 MB ESM 69.74 70.09 57.35 44.06 36.8% shorter
1.8 MB CommonJS 76.14 76.90 58.73 45.71 40.0% shorter

Cold startup (7 fresh processes per case, each with an empty cache)

Script Before After, VPX_CACHE=1 Change in time
Small script 33.03 34.14 +1.11 ms
300-module ESM 72.38 95.13 +22.75 ms
300-module CommonJS 72.84 96.76 +23.92 ms
1.8 MB ESM 70.15 80.09 +9.95 ms
1.8 MB CommonJS 77.55 85.65 +8.10 ms

Packing reduced warm cache file opens from 304 to 2 on both 300-module graphs. Benchmark harness · Packed storage measurements

Scaling to 1,000 and 10,000 modules

One TypeScript entry imports every leaf from the same directory. Both package types use the same static imports and run every leaf before signaling readiness. Only the leaf count and expected sum change from the 300-module graphs. Fixture source

The table shows median milliseconds from global vpx launch to readiness on an Apple M4 Max with Node.js 24.22.2. The comparison uses PR base 0bea91491 and head 2c140295c.

Leaves Package type Cache state Before PR, caches off PR, transform only PR, both caches Both vs. before
1,000 ESM Warm 149.1 149.7 148.2 156.4 4.9% longer
1,000 Default (CommonJS) Warm 149.7 150.4 147.5 157.4 5.2% longer
10,000 ESM Warm 1,197.7 1,204.3 1,394.6 1,560.8 30.3% longer
10,000 Default (CommonJS) Warm 1,193.6 1,210.2 1,394.6 1,547.7 29.7% longer
1,000 ESM Cold 150.1 151.6 211.0 219.7 46.3% longer
1,000 Default (CommonJS) Cold 157.5 151.5 208.6 238.3 51.2% longer
10,000 ESM Cold 1,275.2 1,245.7 2,803.7 2,989.1 134.4% longer
10,000 Default (CommonJS) Cold 1,247.2 1,253.8 2,882.8 3,026.8 142.7% longer

Each mode used 40 warm launches at 1,000 leaves and 30 at 10,000. Each cold median used five launches with empty vpx caches. VPX_CACHE=transform selects the transform cache. VPX_CACHE=1 enables both caches.

At 1,000 leaves, the transform store used two packs and one individual file per graph. At 10,000 leaves, the source directory pack filled to within 1.5 KiB of its 4 MiB limit. Each graph also used 7,174–7,188 individual transform files. Warm diagnostics still recorded 10,004 hits and zero misses per launch. Pack limits and fallback

At 10,000 leaves with empty caches and both layers enabled, median time to process exit was 4.27 s for ESM and 4.35 s for the default package.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-vpx-warm-start-cache-prototype-viteplus-dev.voidzero-docs.workers.dev (commit 2c14029)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://822454d9-viteplus-dev.voidzero-docs.workers.dev 2c14029 2026-11-04T07:50:50.919Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://c659447a-viteplus-dev.voidzero-docs.workers.dev 19d7d80 2026-11-04T02:11:16.572Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://e06a186a-viteplus-dev.voidzero-docs.workers.dev 515df7f 2026-11-04T02:10:17.114Z Visit the dashboard ↗

@fengmk2
fengmk2 force-pushed the feat/vpx-warm-start-cache-prototype branch from 515df7f to 19d7d80 Compare November 4, 2026 02:10
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

CLI artifact sizes (2c14029)

Final release artifacts built by the canonical build-upstream and build-windows-cli actions.
The dist rows use the Linux build. The core total excludes .node files to match the release artifact.

Artifact Format Base PR Change
packages/cli/dist Directory total 2.33 MiB 2.34 MiB +7.22 KiB (+0.30%)
packages/core/dist Directory total 4.08 MiB 4.08 MiB 0 B (0.00%)
Combined package dist Directory total 6.41 MiB 6.42 MiB +7.22 KiB (+0.11%)
vp (Linux x64) Binary 8.31 MiB 8.31 MiB 0 B (0.00%)
vp (Linux x64) gzip -9 3.80 MiB 3.80 MiB 0 B (0.00%)
NAPI (Linux x64) Binary 36.39 MiB 36.45 MiB +64.00 KiB (+0.17%)
NAPI (Linux x64) gzip -9 14.55 MiB 14.58 MiB +30.15 KiB (+0.20%)
vp (macOS ARM64) Binary 5.42 MiB 5.42 MiB 0 B (0.00%)
vp (macOS ARM64) gzip -9 3.23 MiB 3.23 MiB 0 B (0.00%)
NAPI (macOS ARM64) Binary 43.01 MiB 43.05 MiB +48.59 KiB (+0.11%)
NAPI (macOS ARM64) gzip -9 18.64 MiB 18.67 MiB +24.21 KiB (+0.13%)
vp (Windows x64) Binary 6.15 MiB 6.15 MiB 0 B (0.00%)
vp (Windows x64) gzip -9 2.84 MiB 2.84 MiB +1 B (+0.00%)
NAPI (Windows x64) Binary 32.32 MiB 32.38 MiB +57.00 KiB (+0.17%)
NAPI (Windows x64) gzip -9 13.23 MiB 13.25 MiB +25.77 KiB (+0.19%)
Trampoline (Windows x64) Binary 13.50 KiB 13.50 KiB 0 B (0.00%)
Trampoline (Windows x64) gzip -9 7.09 KiB 7.09 KiB 0 B (0.00%)
Installer (Windows x64) Binary 4.66 MiB 4.66 MiB 0 B (0.00%)
Installer (Windows x64) gzip -9 2.23 MiB 2.23 MiB 0 B (0.00%)

fengmk2 commented Nov 8, 2026

Copy link
Copy Markdown
Member Author

Closing this prototype. The measured gains do not justify its storage, invalidation, and compatibility costs for the intended startup improvement.

Follow-up experiments covered packed storage, smaller source maps, asynchronous writes, resolver changes, and a compiled module graph. The results below use later local changes beyond PR head 2c140295c. The graph cache uses a separate experimental preload.

Knip 6.44.2 scanning Marked 18.1.3 produced these warm medians:

Mode Startup Complete scan
Cache disabled 188.5 ms 259.4 ms
Per-module transform cache hit 176.6 ms 247.5 ms
Knip source-graph cache hit 124.2 ms 193.2 ms

Each mode used 120 fresh processes across two interleaved runs on an Apple M4 Max with Node.js 24.22.2. We disabled Node's compile cache and Knip's analysis cache. Startup ends at the first statement in the CLI entry, after its static dependencies execute. Complete scan time includes analysis and process exit. These runs start Node directly with the Vite+ loader and exclude global vpx routing. They cannot be compared directly with the older absolute times in the PR description.

The per-module cache reduced startup by 6.3% and complete scan time by 4.6%, relative to caching disabled. The graph cache reduced those times by 34.1% and 25.5%. Relative to per-module hits, the graph saved 29.7% and 21.9%. It did not meet the goal of reducing elapsed time by at least 50% on this real workload.

The graph prototype reduced startup by 55.0% in a separate 10,000-module synthetic case. Time fell from 860.1 ms with per-module hits to 386.9 ms. The real-project result shows that this gain does not apply to every workload.

The profiles explain the limited benefit of transform caching:

  • Oxc transforms are already fast. Cache keys, source checks, and artifact reads consume part of the saved time.
  • Per-module transform hits still require module resolution, filesystem checks, V8 compilation, linking, and execution.
  • Knip has 425 cacheable source modules. Its scan and reporting still take about 70 ms after entry execution starts.
  • The graph prototype removes more module-loading work, but adds cold construction costs and module-compatibility constraints.

The graph cache combines Knip's static source modules. Third-party packages and native bindings remain external. Each hit includes source-content and configuration checks. Every launch performs a fresh scan. Adding an unused Marked source file changed the report while the graph still hit.

The graph artifact adds about 2.2 MiB of storage. Its two cold runs exited after 589 and 707 ms, compared with 271 and 263 ms for the per-module cache. These are individual observations, not stable cold-start estimates. General dynamic-import and module-identity behavior still needs separate work.

Caching can still help large files with expensive transforms. For the multi-module workloads measured here, further improvements should focus on module loading, V8 compilation, or process reuse. The current evidence does not support merging this cache prototype as a general startup-speed improvement.

@fengmk2 fengmk2 closed this Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant