Flatten all -L search paths into a single list of files - #158823
Conversation
|
r? @mati865 rustbot has assigned @mati865. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
Sorry, meant to open this as a draft, but missclicked. r? @ghost |
This comment has been minimized.
This comment has been minimized.
|
This generally makes sense to me.
Was this considered by cargo team when the new build dir layout was designed? Why wasn't it considered an issue? |
|
I'm sure that it was considered, though I don't know if there were any alternatives. CC @ranger-ross @epage if you have more context. Recently we identified some optimizations that can be made to reduce that effect, they are being landed now (rust-lang/cargo#17168). |
|
Splitting rlibs into unique directories is fairly fundamental to the design. The trivial solution from there is hundreds of |
|
I thought about the mega-symlink-dir approach too :) Might be interesting to try, as that might also have some performance implications. I hope that with rust-lang/cargo#17168 and this PR landing and rust-lang/cargo#17183 being resolved, it will become workable, but we'll have to see how the new build dir layout works in practice to find out. |
8bba6cb to
0e1ba38
Compare
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Flatten all -L search paths into a single list of files
|
@bors try parent=45bc0d28cc57296e36ef653a442e96367ba73d4a |
This comment has been minimized.
This comment has been minimized.
Flatten all -L search paths into a single list of files
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (a31f58e): comparison URL. Overall result: ✅ improvements - no action neededBenchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary -0.9%, secondary -0.8%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary -1.0%, secondary -2.3%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 488.239s -> 487.438s (-0.16%) |
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (6514e27): comparison URL. Overall result: ❌✅ regressions and improvements - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary -0.9%, secondary 0.3%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary -0.8%, secondary -6.7%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 491.471s -> 488.53s (-0.60%) |
9aed7c2 to
2702de0
Compare
|
I have some ideas on what to optimize here, but I think that as a baseline, this is good enough. |
This comment has been minimized.
This comment has been minimized.
2702de0 to
2381c84
Compare
|
Thanks, applied the suggested changes. @rustbot ready |
|
r=me after addressing #158823 (comment) and squashing commits. |
b2dd117 to
55500ae
Compare
|
This PR was rebased onto a different main 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. |
|
@bors r+ |
|
⌛ Testing commit 55500ae with merge 6c04025... Workflow: https://github.com/rust-lang/rust/actions/runs/30669053546 |
Flatten all -L search paths into a single list of files This is an optimization that mostly targets the new [Cargo build dir layout](https://rust-lang.github.io/rust-project-goals/2025h2/cargo-build-dir-layout.html). Before, Cargo usually used to pass rustc a single `-L` path to a directory containing potentially a large number of `.rlib` or other dependency paths. Rustc is optimized for this, and does a preprocessing step, where it preloads all files from this directory into a sorted vector, in which it then performs binary search to look for files having a given prefix and postfix. With the new build dir layout, this changes, and Cargo will pass potentially many (even hundreds, if your crate has many dependencies) `-L` directories, where each directory will usually have only 1-2 files. This is not ideal for the current rustc search implementation, because the old preprocessing isn't really worth it when the `-L` directory typically only has a single file, and because rustc will do a lot of work per each `-L` directory in the lookup phase, including some quadratic (O(n^2)) work. With the `large-workspace` stress test from rustc-perf, where the final binary has ~1000 total and ~500 direct dependencies: - With the old build dir layout, rustc does 4274 calls to `FilesIndex::query` - With the new build dir layout, it does `1938314` calls to `FilesIndex::query`. Not ideal :) This PR modifies the logic so that instead of going through all `-L` directories one by one when we are looking for a specific dependency, we pre-gather all files from all passed `-L` directories at the start, and sort them all together. Then, in the lookup phase, instead of performing <number of -L dirs> * 4 (.rmeta, .rlib, etc.) searched, we only perform the 4 searches across all files. This gets rid of the O(n^2) behavior. Further improvements could be made, e.g. sort the files just once and then filter it for for both host and target filesearchs. I alsothought about pre-filtering the files further, e.g. based on their `kind`, but that is not worth it in usual situations, where ~all of the deps will be `.rlib`s or `.rmeta`s anyway. What might work is pre-filtering based on their actual names (we strip known prefixes from them and then search based on the names alone, in a hashmap or something?), but that can be a follow-up. Diff vs parent. of #159857: https://perf.rust-lang.org/compare.html?start=1a833e16546c2eb012758ddd499964fd8afee29e&end=6514e2707b7e90fecbc570eeaa36f3780a9b02b4&stat=instructions%3Au
View all comments
This is an optimization that mostly targets the new Cargo build dir layout.
Before, Cargo usually used to pass rustc a single
-Lpath to a directory containing potentially a large number of.rlibor other dependency paths. Rustc is optimized for this, and does a preprocessing step, where it preloads all files from this directory into a sorted vector, in which it then performs binary search to look for files having a given prefix and postfix.With the new build dir layout, this changes, and Cargo will pass potentially many (even hundreds, if your crate has many dependencies)
-Ldirectories, where each directory will usually have only 1-2 files. This is not ideal for the current rustc search implementation, because the old preprocessing isn't really worth it when the-Ldirectory typically only has a single file, and because rustc will do a lot of work per each-Ldirectory in the lookup phase, including some quadratic (O(n^2)) work.With the
large-workspacestress test from rustc-perf, where the final binary has ~1000 total and ~500 direct dependencies:FilesIndex::query1938314calls toFilesIndex::query. Not ideal :)This PR modifies the logic so that instead of going through all
-Ldirectories one by one when we are looking for a specific dependency, we pre-gather all files from all passed-Ldirectories at the start, and sort them all together. Then, in the lookup phase, instead of performing <number of -L dirs> * 4 (.rmeta, .rlib, etc.) searched, we only perform the 4 searches across all files. This gets rid of the O(n^2) behavior.Further improvements could be made, e.g. sort the files just once and then filter it for for both host and target filesearchs. I alsothought about pre-filtering the files further, e.g. based on their
kind, but that is not worth it in usual situations, where ~all of the deps will be.rlibs or.rmetas anyway. What might work is pre-filtering based on their actual names (we strip known prefixes from them and then search based on the names alone, in a hashmap or something?), but that can be a follow-up.Diff vs parent. of #159857: https://perf.rust-lang.org/compare.html?start=1a833e16546c2eb012758ddd499964fd8afee29e&end=6514e2707b7e90fecbc570eeaa36f3780a9b02b4&stat=instructions%3Au