Skip to content

perf(gc): batch untracked package directory traversals#17245

Open
hirehamir wants to merge 3 commits into
rust-lang:masterfrom
hirehamir:perf/gc-du-walker-churn
Open

perf(gc): batch untracked package directory traversals#17245
hirehamir wants to merge 3 commits into
rust-lang:masterfrom
hirehamir:perf/gc-du-walker-churn

Conversation

@hirehamir

Copy link
Copy Markdown
Contributor

What Does This PR Try to Resolve?

Addresses part of #13058

Without this change, the code would size every package directory, one du at a time.

Each du spins up its own thread pool.

On an 8-core machine, sizing 500 directories of a few files each spawned ~4000 threads to visit ~3000 files.

With this change, we size them all with one shared pool.

How to Test and Review This PR?

cargo test -p cargo-util --lib du
cargo bench -p benchsuite --bench global_cache_tracker -- global_tracker_sync
case before after speedup
with_size (sizes every dir) 1.15 s 9.5 ms ~120×
age_only (no sizing, control) 2.07 ms 1.92 ms ~1.1×

@rustbot rustbot added A-testing-cargo-itself Area: cargo's tests S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 21, 2026
@rustbot

rustbot commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

r? @weihanglo

rustbot has assigned @weihanglo.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @epage, @weihanglo
  • @epage, @weihanglo expanded to epage, weihanglo
  • Random selection from epage, weihanglo

@epage

epage commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Are there intermediate refactors you could do for this that would make the more relevant to digest parts clearer? See https://epage.github.io/dev/pr-style/#c-split for more context

Comment thread crates/cargo-util/src/du.rs Outdated
Comment on lines 42 to 50
let mut builder = OverrideBuilder::new(root);
let Some((first, rest)) = paths.split_first() else {
return Ok(Vec::new());
};

for pattern in patterns {
builder.add(pattern)?;
}
let overrides = builder.build()?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: please group the creation of builder with the building of it, rather than splitting it around getting first/rest

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll see if I can address all of these right now.

Comment thread crates/cargo-util/src/du.rs Outdated
Comment on lines +68 to +72
// Cargo's hasher isn't reachable from cargo-util.
// The `stat` dominates the walk, so the hasher speed shouldn't make much difference here.
#[allow(clippy::disallowed_types)]
let path_to_index: std::collections::HashMap<&Path, usize> =
paths.iter().copied().zip(0..).collect();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we address that by adding a dependency?

@epage

epage commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Note: did a cursory look only

The existing benchmarks cover updating an already-populated database.
Syncing one with the files on disk is the expensive part, and is what a
cache left behind by a cargo that didn't track it has to go through.

Sizing those files is a separate step, only taken when a size limit is
given, so measure it both ways.
`du` builds a thread pool of its own.

For a small directory, that costs far more than the walk itself.

Sizing many directories by calling `du` in a loop pays that cost every time.

`du_each` walks any number of directories with a single pool.

It keeps a running total per directory so each still gets its own size.

`du` becomes a thin wrapper over it.
This code originally spent far more time on starting threads than traversal.

On an 8-core machine, sizing 500 directories of a few files each spawned
~4000 threads to visit ~3000 files.

This change traverses them all with a single pool instead, keeping a
total per directory so each still gets its own size.

The same case now spawns 8 threads and cuts context switches from ~6400 to ~80.

The global_tracker_sync benchmark, syncing 500 such directories with
--max-download-size, drops from 1.15s to 9.5ms - a 120x speedup.
@hirehamir
hirehamir force-pushed the perf/gc-du-walker-churn branch from ed7f675 to 3ca4e5b Compare July 21, 2026 23:29
@rustbot

rustbot commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@hirehamir
hirehamir requested a review from epage July 21, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-testing-cargo-itself Area: cargo's tests S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants