Commit 9fef3f5
authored
perf: Race parallel git subprocesses against filesystem walk for optimal index construction (#12206)
## Summary
- Replaces `new_from_gix_index` (stat every tracked file) +
`walk_candidate_files` with a faster hybrid approach
- Uses `git ls-tree` + `git diff-index` subprocesses for the tracked
index (simpler, fast everywhere)
- **Races** `walk_candidate_files` (8-thread ignore-crate walk) against
`git ls-files --others` (git subprocess) for untracked file discovery —
whichever finishes first wins
- The race guarantees optimal performance on every platform without
platform-specific code paths
## Why
No single untracked-file discovery method is fastest everywhere:
| Method | macOS APFS | Linux ext4 |
|---|---|---|
| `walk_candidate_files` (8 threads) | **~440ms** | ~474ms |
| `git ls-files --others` (single thread) | ~530ms | **~231ms** |
Racing both and using the winner eliminates regressions on either
platform.
## How the race works
Four operations spawn on separate threads:
1. `git ls-tree -r HEAD -z` — blob OIDs (~60-110ms)
2. `git diff-index HEAD -z` — modified/deleted (~95-150ms)
3. `walk_candidate_files` — 8-thread filesystem walk
4. `git ls-files --others -z` — git subprocess
Operations 3 and 4 send results through an `mpsc` channel. The first
result wins. The losing thread runs to completion and its result is
discarded.
If `ls-files` wins: its output is the untracked file list directly.
If `walk` wins: candidates are filtered against `ls-tree` hashes to find
untracked files.
## Benchmark (110-package monorepo, 30 runs, sandboxed Linux)
| | Mean | Min | Max |
|---|---|---|---|
| Baseline (main) | 878ms ± 27ms | 840ms | 953ms |
| This PR | **437ms ± 7ms** | 427ms | 455ms |
**2.01x faster** on Linux. No regression on macOS (walk wins the race at
~440ms, same as the split-walk approach).
## Test Coverage
18 new regression tests across three categories:
**Category 7 — Ground truth** (8 tests): Establish correct per-package
hashes across edge cases (staged changes/new files/deletions, unstaged
modifications, no-commit repos, comprehensive mixed state).
**Category 8 — Subprocess+race equivalence** (5 tests): Verify the
race-based constructor produces identical results to gix-index and
no-index paths.
**Category 9 — Race arm equivalence** (5 tests): Independently verify
each arm of the race (walk path and ls-files path) produces identical
results, so the winner is always correct regardless of which arm wins.
## How to Review
1. `repo_index.rs` — `new_from_subprocess_and_walk`: the race
implementation with `mpsc` channel
2. `ls_tree.rs` — `git_ls_tree_repo_root_sorted`,
`git_diff_index_repo_root`, `git_ls_files_untracked` + parsers
3. `lib.rs` — `build_repo_index_from_subprocesses` accepts prefixes,
calls new constructor
4. `builder.rs` — Passes `all_package_prefixes` into the method
5. `git_index_regression_tests.rs` — `build_walk_arm_index`,
`build_ls_files_arm_index` helpers + 18 new tests1 parent a9bbb9e commit 9fef3f5
File tree
5 files changed
+1025
-52
lines changed- crates
- turborepo-lib/src/run
- turborepo-scm/src
5 files changed
+1025
-52
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
261 | 261 | | |
262 | 262 | | |
263 | 263 | | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | 264 | | |
269 | 265 | | |
270 | 266 | | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
282 | 270 | | |
283 | 271 | | |
284 | 272 | | |
| |||
354 | 342 | | |
355 | 343 | | |
356 | 344 | | |
357 | | - | |
358 | | - | |
359 | | - | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
360 | 349 | | |
361 | | - | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
362 | 355 | | |
363 | 356 | | |
364 | | - | |
365 | | - | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
373 | | - | |
374 | | - | |
375 | | - | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
376 | 361 | | |
377 | 362 | | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | 363 | | |
391 | 364 | | |
392 | 365 | | |
| |||
497 | 470 | | |
498 | 471 | | |
499 | 472 | | |
500 | | - | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | 473 | | |
508 | 474 | | |
509 | 475 | | |
| |||
0 commit comments