Why vx is fast
vx’s speed isn’t a microbenchmark trick; it comes from a handful of structural decisions. This page explains them. The exhaustive, source-cited catalog lives in Optimizations and the raw numbers in Benchmarks.
The numbers
Section titled “The numbers”These are reproducible on your own machine, not marketing figures:
-
Runner overhead, the number to read first: the time a runner adds on top of the ideal run. On the synthetic 9,603-task workspace, with nothing changed, vx adds 469 ms, Turborepo 531 ms, Vite Task 8.40 s and Nx 9.90 s. On a cold build over its 1 min 53 s ideal schedule, vx adds 7.04 s, Turborepo 9.57 s, Nx 2 min 10 s and Vite Task 8.47 s. The workload: 1,601 projects in 30 dependency levels; build 300 ms, test and typecheck 150 ms, lint 75 ms, publish 30 ms. Measured: vx 0.0.0 @ ef6a82201 on 2026-11-12, Turborepo 2.11.10 on 2026-11-12, Nx 24.0.2 on 2026-11-12, Vite Task 1.4.0 on 2026-11-12; linux x64, 4 cores (AMD EPYC 9V74 80-Core Processor), 16 GB.
-
vx alone —
bun private/vx-bench/run.ts [projects]measures vx across fresh / warm-no-restore / warm-restore, from avx locksnapshot, as every run reads one. A 100-project workspace replayed fully-cached in 74 ms whole-process (1,000 projects in 172 ms) on one macOS arm64 machine (2026-09-02); a 4-core Linux container reads 271 ms at 1,000 projects. Its restore row deletes the outputs first and extracts every artifact, so it costs more than the untouched tree; the current floors are in Benchmarks. -
Head-to-head vs Turborepo, Nx and Vite Task —
bun private/vx-bench/compare.tsscaffolds one repo (1,601 projects, 30 dependency levels, 9,603 tasks) and runs every runner cold, with nothing changed and with outputs restored. The site’s figures are its latest run, on Benchmarks. Run it yourself — every number here is a command away.
What others don’t have
Section titled “What others don’t have”These are correctness and speed wins — they make the cache both safer and faster:
- Sparse
^taskbridging.^buildwalks through dependency packages that don’t declare the task to the nearest one that does, so sparse task coverage doesn’t need no-op filler tasks, and the graph carries no placeholder nodes for the packages walked through. - Resolved-config hashing. vx hashes the evaluated
vx.config.tsobject, so imports, presets, and computed values participate in the key. Static-JSON config can’t see them. - Strict output ownership. Declared outputs are wiped before exec,
and a restore removes what its entry does not hold, so the tree ends
as the cached snapshot, with no stale files. Two tasks whose outputs overlap are refused by default
(
rules.exclusiveOutputs); with that rule off, a task that adds files to an upstream task’s outputs cleans nothing before exec and only the files it recorded before a restore. Turborepo/Nx restore additively. - Daemonless. No background process, no staleness window, no socket to corrupt, and the head-to-head benchmark shows what that costs on a cached run.
The mechanics under the hood
Section titled “The mechanics under the hood”- Hashes come straight from git’s index. One
git ls-files -sspawn yields the tracked file list and every clean file’s blob OID; a concurrentgit statusprunes anything that diverges and lists untracked files. Clean-tree key derivation costs zero source-file reads, zero stats, zero database lookups. Dirty files get the identical blob OID computed in-process, so a key never flips across a commit boundary — a class of spurious miss the others accept. - Bitset graph algorithms. Scheduler priority and the package graph use packed-bitset closures with popcount instead of set-union DFS. On a 3,270-task graph this turned an 8.5 s priority computation into single-digit milliseconds.
- A scheduler tick that re-scans nothing. Ready tasks come off an
exact most-blocked-first binary heap; a completion decrements its
direct dependents’ counters and pushes the ones that reach zero, so
the run costs one pass over the edges plus an
O(log N)heap operation per task, never a re-scan of the graph per completion. - Stat-check restore skips. A warm-on-warm restore is N stats with zero writes and zero decompression — fingerprints in SQLite tell vx the tree is already current.
- One artifact format end to end. Local and remote move the same
tar.zstbytes — metadata rides SQLite locally and the remote’s own record on the wire, so nothing is repacked at the boundary. - In-process tar, atomic publish, single-transaction SQL, and collision-hardened xxh3 key derivation round it out.
Things vx deliberately didn’t build
Section titled “Things vx deliberately didn’t build”Speed by subtraction is still speed:
- No daemon / project-graph process — the cold numbers say it isn’t needed.
- No config evaluation in a run — configs are programs, and a run
evaluates none: it reads the resolved objects from
.vx/projects.lock.json. Onlyvx lockevaluates them. - No filesystem-tracing auto-inputs. Not a gap — a position. A
traced input set describes what the task read that time, on that
machine, which is not the same as what it depends on; and it cannot be
known before the task runs, which is exactly when the key is needed.
vx asks you to declare inputs and gives you a boundary to check them
against: inside the workspace, a task with
sandboxreads only whatallow.readgrants, plusnode_modulesand the linked packages it depends on. Reads outside the workspace stay open, except the credential stores under home (~/.ssh,~/.aws, …), andinputsgrants nothing, so the sandbox checks the input list only whereallow.readmirrors it. Guessing is replaced by a boundary.
See where the time goes
Section titled “See where the time goes”VX_DEBUG_TIMING=1 prints each stage of a run on stderr. A cached one-task
run on a 4-core Linux container:
$ VX_DEBUG_TIMING=1 vx run @demo/app#build[vx timing] stage own cumulative startup 58.9ms 58.9ms workspace config 4.7ms 63.6ms discover projects 4.6ms 68.2ms load configs 5.7ms 81.9ms build graph 3.3ms 85.2ms git enumeration 8.8ms 93.9ms classify + probe 15.7ms 109.8ms run graph 8.5ms 118.3ms close 3.9ms 125.7msRows under 1 ms are cut here. See Timing for the full table.
Go deeper
Section titled “Go deeper”- Optimizations — every decision with its invariant and source.
- Benchmarks — methodology and full results.
- vx vs Turborepo vs Nx — feature-by-feature.