Skip to content

fix quadratic naming of duplicate sidebar links - #162976

Open
xonx4l wants to merge 2 commits into
rust-lang:mainfrom
xonx4l:rustdoc-sidebar-quadratic
Open

xonx4l wants to merge 2 commits into
rust-lang:mainfrom
xonx4l:rustdoc-sidebar-quadratic

Conversation

@xonx4l

@xonx4l xonx4l commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

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