Skip to content

fix quadratic naming of duplicate sidebar links - #162976

Merged
rust-bors[bot] merged 2 commits into
rust-lang:mainfrom
xonx4l:rustdoc-sidebar-quadratic
Sep 24, 2026
Merged

rust-bors[bot] merged 2 commits into
rust-lang:mainfrom
xonx4l:rustdoc-sidebar-quadratic

Conversation

@xonx4l

@xonx4l xonx4l commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

View all comments

This PR fixes quadratic naming of duplicate sidebar links as discussed in #158174.

Fixes #158174

get_next_url gives duplicate sidebar links with unique names but it started counting from 1 every time which makes the page quadratic.

So to make it more optimal I changed that to remember the next unique number for each name making it linear per page.

Also added the test at tests/rustdoc-html/deref/sidebar-links-deref-chain.rs

Results

N Instructions (before) Instructions (after) Time (before) Time (after)
200 5.71 B 3.69 B 1.20 s 1.01 s
400 22.40 B 9.52 B 3.79 s 1.98 s
800 126.83 B 30.82 B 19.68 s 6.04 s
1600 — — 118.59 s 37.76 s

r? @GuillaumeGomez

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output. labels Sep 18, 2026
@GuillaumeGomez

GuillaumeGomez commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Implementation looks good to me. Let's confirm the perf gain.

Also I'd like @lolbinarycat to have a look as she worked on this recently (iirc).

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Sep 18, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Sep 18, 2026
fix quadratic naming of duplicate sidebar links
@rust-bors

rust-bors Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: cc18f18 (cc18f1802395cbbddd322ff59434f18580ea7cc7)
Base parent: 420ed2a (420ed2a0c3d7225b1744266fd884d431b4d8cfe0)

@rust-timer

This comment has been minimized.

@@ -0,0 +1,21 @@
// Regression test for <https://github.com/rust-lang/rust/issues/158174>.

@lolbinarycat lolbinarycat Sep 18, 2026 •

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.

I feel like this should be a rustc-perf benchmark, not a regular test, since this is a performance improvement, not a bugfix. @GuillaumeGomez what do you think?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not both? =D

But yes, very good idea!

Comment thread src/librustdoc/html/render/sidebar.rs Outdated
Comment on lines 750 to 767
struct UsedLinks {
links: FxHashSet<String>,
/// Next number to try for each anchor.
next_suffix: FxHashMap<String, usize>,
}

fn get_next_url(used_links: &mut UsedLinks, url: String) -> String {
if used_links.links.insert(url.clone()) {
return url;
}
let mut add = 1;
while !used_links.insert(format!("{url}-{add}")) {
add += 1;
let add = used_links.next_suffix.entry(url.clone()).or_insert(1);
loop {
let candidate = format!("{url}-{add}");
*add += 1;
if used_links.links.insert(candidate.clone()) {
return candidate;
}
}

@lolbinarycat lolbinarycat Sep 18, 2026 •

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.

Unless I'm missing an edge case somehow, this should be doable with 1 hashmap and no loop.

Suggested change
struct UsedLinks {
links: FxHashSet<String>,
/// Next number to try for each anchor.
next_suffix: FxHashMap<String, usize>,
}
fn get_next_url(used_links: &mut UsedLinks, url: String) -> String {
if used_links.links.insert(url.clone()) {
return url;
}
let mut add = 1;
while !used_links.insert(format!("{url}-{add}")) {
add += 1;
let add = used_links.next_suffix.entry(url.clone()).or_insert(1);
loop {
let candidate = format!("{url}-{add}");
*add += 1;
if used_links.links.insert(candidate.clone()) {
return candidate;
}
}
type UsedLinks = FxHashMap<String, usize>;
fn get_next_url(used_links: &mut UsedLinks, url: String) -> String {
let count = used_links.entry(url.clone()).or_insert(0);
let res = if count == 0 { url } else { format!("{url}-{count}") };
*count += 1;
res
}

View changes since the review

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.

Yes, a counter is enough and better indeed . Will make the change with a bit of correctness here ( *count == 0) as in the suggested snippet it compares a &mut usize with a number . Which won't compile . Thank you for the review!

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (cc18f18): comparison URL.

Overall result: no relevant changes - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

This perf run didn't have relevant results for this metric.

Max RSS (memory usage)

Results (primary 3.8%, secondary -3.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
3.8% [3.8%, 3.8%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.7% [-3.7%, -3.7%] 1
All ❌✅ (primary) 3.8% [3.8%, 3.8%] 1

Cycles

Results (secondary -3.2%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.2% [-3.2%, -3.2%] 1
All ❌✅ (primary) - - 0

Binary size

Results (primary -0.1%, secondary -0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-0.1% [-0.2%, -0.0%] 71
Improvements ✅
(secondary)
-0.1% [-0.2%, -0.0%] 53
All ❌✅ (primary) -0.1% [-0.2%, -0.0%] 71

Bootstrap: 498.333s -> 499.277s (0.19%)
Artifact size: 408.93 MiB -> 408.87 MiB (-0.01%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Sep 18, 2026
@GuillaumeGomez

Copy link
Copy Markdown
Member

Hum... Do we even have a test to stress test the sidebar? Should we add the new sidebar perf test?

@lolbinarycat

Copy link
Copy Markdown
Contributor

Hum... Do we even have a test to stress test the sidebar?

it doesn't seem like it

Should we add the new sidebar perf test?

yes, that would be good, maybe even add a larger test using the python script in the original issue.

@Kobzol

Kobzol commented Sep 19, 2026

Copy link
Copy Markdown
Member

If the perf. regression can be shown with a simple crate, I think it would be fine to add it as a stress test benchmark to rustc-perf.

@xonx4l

xonx4l commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! I'll open a follow-up rustc-perf PR with a stress benchmark based on the script from #158174 .

@GuillaumeGomez

Copy link
Copy Markdown
Member

Please add the new perf test first, like that we can have a more precise view of the impact of this PR (in addition to the numbers you already provided).

@Kobzol

Kobzol commented Sep 23, 2026

Copy link
Copy Markdown
Member

After the next PR is merged by bors, we can do a perf. run here, rustc-perf should now contain the benchmark.

@GuillaumeGomez

Copy link
Copy Markdown
Member

Since it contains the perf, can't we do it now?

@Kobzol

Kobzol commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Not really, because there would be no parent main commit to compare to. Or rather, the parent would not contain the new benchmark yet.

@GuillaumeGomez

Copy link
Copy Markdown
Member

We just need a new merge in-between then? So we can wait for next merge on rust. Or I'm missing something obvious. ^^'

@Kobzol

Kobzol commented Sep 23, 2026

Copy link
Copy Markdown
Member

Yes, that's in fact what I meant by "After the next PR is merged by bors" 😆

@GuillaumeGomez

Copy link
Copy Markdown
Member

Ok so we were saying the same thing. Perfect! 🤣

@Kobzol

Kobzol commented Sep 23, 2026

Copy link
Copy Markdown
Member

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Sep 23, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Sep 23, 2026
fix quadratic naming of duplicate sidebar links
@rust-bors

rust-bors Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 16d9f56 (16d9f566ab8b344e190a48387562ae5e90e6da9a)
Base parent: a92214c (a92214c92df8cc0261f5ab5c08c6376c2c03a958)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (16d9f56): comparison URL.

Overall result: ✅ improvements - no action needed

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.

@bors rollup=never rustc-perf
@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-82.5% [-82.5%, -82.5%] 1
All ❌✅ (primary) - - 0

Max RSS (memory usage)

Results (secondary -1.6%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.6% [0.6%, 0.6%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.7% [-3.7%, -3.7%] 1
All ❌✅ (primary) - - 0

Cycles

Results (primary 2.8%, secondary -33.9%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.8% [2.8%, 2.8%] 1
Regressions ❌
(secondary)
4.2% [4.2%, 4.2%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-72.1% [-72.1%, -72.1%] 1
All ❌✅ (primary) 2.8% [2.8%, 2.8%] 1

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 501.272s -> 489.678s (-2.31%)
Artifact size: 406.37 MiB -> 406.39 MiB (0.00%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Sep 23, 2026
@GuillaumeGomez

Copy link
Copy Markdown
Member

Wow. Now that's an impressive improvement. Congrats!

@bors r=lolbinarycat,GuillaumeGomez rollup=iffy

@rust-bors

rust-bors Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 1875276 has been approved by lolbinarycat,GuillaumeGomez

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 23, 2026
jhpratt added a commit to jhpratt/rust that referenced this pull request Sep 23, 2026
…r=lolbinarycat,GuillaumeGomez

fix quadratic naming of duplicate sidebar links

This PR fixes quadratic naming of duplicate sidebar links as discussed in rust-lang#158174.

Fixes rust-lang#158174

`get_next_url` gives duplicate sidebar links with unique names but it started counting from 1 every time which makes the page quadratic.

So to make it more optimal I changed that to remember the next  unique number for each name  making it linear per page.

Also added the test at `tests/rustdoc-html/deref/sidebar-links-deref-chain.rs`

### Results

| N    | Instructions (before) | Instructions (after) | Time (before) | Time (after) |
|-----:|----------------------:|---------------------:|--------------:|-------------:|
| 200  | 5.71 B                | 3.69 B               | 1.20 s        | 1.01 s       |
| 400  | 22.40 B               | 9.52 B               | 3.79 s        | 1.98 s       |
| 800  | 126.83 B              | 30.82 B              | 19.68 s       | 6.04 s       |
| 1600 | —                     | —                    | 118.59 s      | 37.76 s      |

r? @GuillaumeGomez
rust-bors Bot pushed a commit that referenced this pull request Sep 24, 2026
Rollup of 8 pull requests

Successful merges:

 - #162976 (fix quadratic naming of duplicate sidebar links)
 - #163222 (Enable EII tests for cg_gcc)
 - #162942 (Remove `StashKey::AssociatedTypeSuggestion`)
 - #163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates)
 - #163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable)
 - #163185 (properly decrement available_depth on cycles and provisional cache hits)
 - #163214 (revert r14 register names for arm)
 - #163226 (miri subtree update)
rust-bors Bot pushed a commit that referenced this pull request Sep 24, 2026
Rollup of 8 pull requests

Successful merges:

 - #162976 (fix quadratic naming of duplicate sidebar links)
 - #163222 (Enable EII tests for cg_gcc)
 - #162942 (Remove `StashKey::AssociatedTypeSuggestion`)
 - #163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates)
 - #163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable)
 - #163185 (properly decrement available_depth on cycles and provisional cache hits)
 - #163214 (revert r14 register names for arm)
 - #163226 (miri subtree update)
rust-bors Bot pushed a commit that referenced this pull request Sep 24, 2026
…uwer

Rollup of 14 pull requests

Successful merges:

 - #162976 (fix quadratic naming of duplicate sidebar links)
 - #161275 (Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test)
 - #163143 (cg_llvm: Use fewer FFI calls to check the target CPU's features)
 - #163188 (Adjust for Arm64EC name mangling when checking for exported symbols)
 - #163211 (`rustc_builtin_macros` cleanup, part 6)
 - #161386 (Don't merge distinct impl candidates)
 - #162942 (Remove `StashKey::AssociatedTypeSuggestion`)
 - #163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates)
 - #163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable)
 - #163185 (properly decrement available_depth on cycles and provisional cache hits)
 - #163214 (revert r14 register names for arm)
 - #163226 (miri subtree update)
 - #163228 (Add regression test for trait predicate with escaping bounds)
 - #163234 (`rustc_dump_symbol_name`: add demangling information as a note instead)
@rust-bors
rust-bors Bot merged commit b255ecd into rust-lang:main Sep 24, 2026
14 checks passed
rust-bors Bot pushed a commit that referenced this pull request Sep 24, 2026
Rollup merge of #162976 - xonx4l:rustdoc-sidebar-quadratic, r=lolbinarycat,GuillaumeGomez

fix quadratic naming of duplicate sidebar links

This PR fixes quadratic naming of duplicate sidebar links as discussed in #158174.

Fixes #158174

`get_next_url` gives duplicate sidebar links with unique names but it started counting from 1 every time which makes the page quadratic.

So to make it more optimal I changed that to remember the next  unique number for each name  making it linear per page.

Also added the test at `tests/rustdoc-html/deref/sidebar-links-deref-chain.rs`

### Results

| N    | Instructions (before) | Instructions (after) | Time (before) | Time (after) |
|-----:|----------------------:|---------------------:|--------------:|-------------:|
| 200  | 5.71 B                | 3.69 B               | 1.20 s        | 1.01 s       |
| 400  | 22.40 B               | 9.52 B               | 3.79 s        | 1.98 s       |
| 800  | 126.83 B              | 30.82 B              | 19.68 s       | 6.04 s       |
| 1600 | —                     | —                    | 118.59 s      | 37.76 s      |

r? @GuillaumeGomez
@rustbot rustbot added this to the 1.100.0 milestone Sep 24, 2026
@rust-timer

Copy link
Copy Markdown
Collaborator

Note

This PR was benchmarked as part of triage of its containing rollup: triage URL.

Finished benchmarking commit (303bef3): comparison URL.

Overall result: ✅ improvements - no action needed

@rustbot label: -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-82.5% [-82.5%, -82.5%] 1
All ❌✅ (primary) - - 0

Max RSS (memory usage)

This perf run didn't have relevant results for this metric.

Cycles

Results (secondary -34.5%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
3.0% [3.0%, 3.0%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-72.0% [-72.0%, -72.0%] 1
All ❌✅ (primary) - - 0

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: missing data
Artifact size: 406.30 MiB -> 406.32 MiB (0.01%)

pull Bot pushed a commit to xtqqczze/rust-lang-miri that referenced this pull request Sep 25, 2026
…uwer

Rollup of 14 pull requests

Successful merges:

 - rust-lang/rust#162976 (fix quadratic naming of duplicate sidebar links)
 - rust-lang/rust#161275 (Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test)
 - rust-lang/rust#163143 (cg_llvm: Use fewer FFI calls to check the target CPU's features)
 - rust-lang/rust#163188 (Adjust for Arm64EC name mangling when checking for exported symbols)
 - rust-lang/rust#163211 (`rustc_builtin_macros` cleanup, part 6)
 - rust-lang/rust#161386 (Don't merge distinct impl candidates)
 - rust-lang/rust#162942 (Remove `StashKey::AssociatedTypeSuggestion`)
 - rust-lang/rust#163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates)
 - rust-lang/rust#163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable)
 - rust-lang/rust#163185 (properly decrement available_depth on cycles and provisional cache hits)
 - rust-lang/rust#163214 (revert r14 register names for arm)
 - rust-lang/rust#163226 (miri subtree update)
 - rust-lang/rust#163228 (Add regression test for trait predicate with escaping bounds)
 - rust-lang/rust#163234 (`rustc_dump_symbol_name`: add demangling information as a note instead)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rustdoc sidebar duplicate-anchor de-duplication appears to be quadratic

6 participants