Skip to content

Avoid cloning parsed TOML manifest in ManifestErrorContext#17167

Merged
epage merged 1 commit into
rust-lang:masterfrom
Kobzol:arc-manifest
Jul 2, 2026
Merged

Avoid cloning parsed TOML manifest in ManifestErrorContext#17167
epage merged 1 commit into
rust-lang:masterfrom
Kobzol:arc-manifest

Conversation

@Kobzol

@Kobzol Kobzol commented Jul 2, 2026

Copy link
Copy Markdown
Member

What does this PR try to resolve?

I was profiling the build performance of bors, and noticed that running Cargo on a no-op build takes >300ms, which seemed like a lot. I found a few places that could be micro-optimized, this one is the one that produced the largest performance win with the fewest code changes.

I noticed that ManifestErrorContext is being unconditionally created for each compiled unit (whether fresh or not), even though in the vast majority of cases, there are no manifest errors to be enriched. The error context clones a bunch of things. While taking a look at memory profiles using Valgrind's dhat, I noticed that a lot of time and memory allocations were spent on cloning the TOML document, which happened inside the error context.

This PR changes the representation of the TOML manifest from Rc to Arc, so that it can be directly used in ManifestErrorContext without cloning the whole thing.

This produced a ~6% wall-time win on the Cargo invocation on bors:

Benchmark 1: /projects/personal/rust/cargo/target/release/cargo test --no-run
Time (mean ± σ):     317.8 ms ±   1.0 ms    [User: 204.1 ms, System: 117.6 ms]
Range (min … max):   316.5 ms … 319.7 ms    10 runs

Benchmark 2: ./cargo-baseline test --no-run
Time (mean ± σ):     336.4 ms ±   1.3 ms    [User: 218.7 ms, System: 121.4 ms]
Range (min … max):   334.8 ms … 339.5 ms    10 runs

Summary
/projects/personal/rust/cargo/target/release/cargo test --no-run ran
1.06 ± 0.01 times faster than ./cargo-baseline test --no-run

I compiled Cargo with lto = "thin" to emulate the configuration we use in the Cargo that is distributed through Rustup.

How to test and review this PR?

I suppose try to run and benchmark Cargo on a project with hundreds of deps before and after this change :)

@rustbot rustbot added A-build-execution Area: anything dealing with executing the compiler A-manifest Area: Cargo.toml issues S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 2, 2026
@rustbot

rustbot commented Jul 2, 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: @ehuss, @epage, @weihanglo
  • @ehuss, @epage, @weihanglo expanded to ehuss, epage, weihanglo
  • Random selection from ehuss, epage, weihanglo

@epage epage left a comment

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.

Thanks!

This is a newer, experimental code path so we'll see if it sticks around but worth improving in the mean time.

I suspect we'll eventually move more Rcs to Arcs over time.

View changes since this review

@epage epage enabled auto-merge July 2, 2026 11:53
@Kobzol

Kobzol commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Makes sense. Another big part of allocations and performance is parsing the TOML manifests, which is of course a "known bottleneck" :) I wonder if there is some opportunity for zero-copy references into the original input, either for the TOML manifest itself, or at least for data derived from it, e.g. summaries.

@epage

epage commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

I've wanted to improve things there.
If you have any ideas, would love to hear them. Typically, I have run into one or more of

  • managing lifetimes
  • barriers with serde
  • keeping cargo-util-schemas usable by others
  • specialized, invasive toml changes
  • design trade offs in toml

@epage epage added this pull request to the merge queue Jul 2, 2026
Merged via the queue into rust-lang:master with commit 400deed Jul 2, 2026
29 checks passed
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 2, 2026
@Kobzol Kobzol deleted the arc-manifest branch July 2, 2026 19:42
@rustbot rustbot added this to the 1.99.0 milestone Jul 5, 2026
pull Bot pushed a commit to johnperez416/cargo that referenced this pull request Jul 6, 2026
*[View all
comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/cargo/pull/17169)*

### What does this PR try to resolve?

While profiling the build performance of bors, one of the things that
kind of screamed me in the performance profiles were many calls to
`SipHasher`, the default hashing algorithm from the Rust standard
library.

I tried to see what would be the performance effect of:
1. Replacing the hottest instances of hashing (in HashMap and HashSet)
that I found in the profiles by `rustc_hash::{HashMap, HashSet}` (first
commit)
2. Replacing pretty much all hashmaps and hashsets in Cargo with the
`FxHasher` maps/sets from `rustc_hash` (second commit)

Note that the benchmark results below are based on
rust-lang#17167.

## First commit
```
Benchmark 1: /projects/personal/rust/cargo/target/release/cargo test --no-run
Time (mean ± σ):     315.2 ms ±   2.2 ms    [User: 206.2 ms, System: 111.9 ms]
Range (min … max):   312.2 ms … 318.3 ms    10 runs

Benchmark 2: ./cargo-baseline test --no-run
Time (mean ± σ):     319.2 ms ±   1.2 ms    [User: 210.6 ms, System: 112.3 ms]
Range (min … max):   317.4 ms … 320.7 ms    10 runs

Summary
/projects/personal/rust/cargo/target/release/cargo test --no-run ran
1.01 ± 0.01 times faster than ./cargo-baseline test --no-run

# perf stat (baseline)
1 301 866 736      instructions

# perf stat (new)
1 244 381 900      instructions
```

Approximately a 1% win on wall-time, and 4.5% on instruction counts.

## Second commit
```
Benchmark 1: ./cargo-perf test --no-run
Time (mean ± σ):     305.5 ms ±   2.0 ms    [User: 199.1 ms, System: 110.2 ms]
Range (min … max):   303.8 ms … 310.6 ms    10 runs

Benchmark 2: ./cargo-baseline test --no-run
Time (mean ± σ):     318.7 ms ±   1.9 ms    [User: 205.6 ms, System: 116.9 ms]
Range (min … max):   317.1 ms … 321.7 ms    10 runs

Summary
./cargo-perf test --no-run ran
1.04 ± 0.01 times faster than ./cargo-baseline test --no-run

# perf stat (baseline)
1 302 028 601      instructions

# perf stat (new)
1 192 872 611      instructions
```

Approximately a 4% win on wall-time and 9% on instruction counts.

Similar micro-optimizations are a bit tricky, and I'd understand if you
wouldn't want to land this. On one hand, the first commit has relatively
few changes, but the perf. wins are much smaller than with the second
commit. But the second commit has a lot of changes, and some of them are
not performance-sensitive (but the wins are kind of spread throughout
Cargo, so it's very difficult to estimate which ones are worth it or
not).

For my personal projects where I care about performance and don't care
about DoS hashing attacks, I usually create an alias for a `HashMap` and
`HashSet` that uses e.g. `FxHasher`, and ideally also `hashbrown` with
the `inline-more` feature. `rustc` kind of does the same, and tries to
use the Fx hash maps/sets everywhere to have better performance overall.

I think that Cargo could do the same, but I don't know if you'd like to
have different conventions about it than what I did in the second
commit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-build-execution Area: anything dealing with executing the compiler A-manifest Area: Cargo.toml issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants