Skip to content

Commit 10b1c9a

Browse files
nnethercotecamelid
authored andcommitted
rustdoc: avoid many Symbol to String conversions.
Particularly when constructing file paths and fully qualified paths. This avoids a lot of allocations, speeding things up on almost all examples.
1 parent 02c9e73 commit 10b1c9a

File tree

13 files changed

+198
-120
lines changed

13 files changed

+198
-120
lines changed

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ symbols! {
567567
doc_spotlight,
568568
doctest,
569569
document_private_items,
570+
dotdot: "..",
570571
dotdot_in_tuple_patterns,
571572
dotdoteq_in_patterns,
572573
dreg,

src/librustdoc/clean/inline.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use rustc_data_structures::thin_vec::ThinVec;
99
use rustc_hir as hir;
1010
use rustc_hir::def::{DefKind, Res};
1111
use rustc_hir::def_id::DefId;
12-
use rustc_hir::definitions::DefPathData;
1312
use rustc_hir::Mutability;
1413
use rustc_metadata::creader::{CStore, LoadedMacro};
1514
use rustc_middle::ty::{self, TyCtxt};
@@ -164,12 +163,10 @@ crate fn load_attrs<'hir>(cx: &DocContext<'hir>, did: DefId) -> Attrs<'hir> {
164163
/// These names are used later on by HTML rendering to generate things like
165164
/// source links back to the original item.
166165
crate fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemType) {
167-
let crate_name = cx.tcx.crate_name(did.krate).to_string();
166+
let crate_name = cx.tcx.crate_name(did.krate);
168167

169-
let relative = cx.tcx.def_path(did).data.into_iter().filter_map(|elem| {
170-
// Filter out extern blocks
171-
(elem.data != DefPathData::ForeignMod).then(|| elem.data.to_string())
172-
});
168+
let relative =
169+
cx.tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
173170
let fqn = if let ItemType::Macro = kind {
174171
// Check to see if it is a macro 2.0 or built-in macro
175172
if matches!(

src/librustdoc/formats/cache.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
44
use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX};
55
use rustc_middle::middle::privacy::AccessLevels;
66
use rustc_middle::ty::TyCtxt;
7-
use rustc_span::symbol::sym;
7+
use rustc_span::{sym, Symbol};
88

99
use crate::clean::{self, types::ExternalLocation, ExternalCrate, ItemId, PrimitiveType};
1010
use crate::core::DocContext;
1111
use crate::fold::DocFolder;
1212
use crate::formats::item_type::ItemType;
1313
use crate::formats::Impl;
14+
use crate::html::format::join_with_double_colon;
1415
use crate::html::markdown::short_markdown_summary;
1516
use crate::html::render::search_index::get_function_type_for_search;
1617
use crate::html::render::IndexItem;
@@ -39,11 +40,11 @@ crate struct Cache {
3940
/// URLs when a type is being linked to. External paths are not located in
4041
/// this map because the `External` type itself has all the information
4142
/// necessary.
42-
crate paths: FxHashMap<DefId, (Vec<String>, ItemType)>,
43+
crate paths: FxHashMap<DefId, (Vec<Symbol>, ItemType)>,
4344

4445
/// Similar to `paths`, but only holds external paths. This is only used for
4546
/// generating explicit hyperlinks to other crates.
46-
crate external_paths: FxHashMap<DefId, (Vec<String>, ItemType)>,
47+
crate external_paths: FxHashMap<DefId, (Vec<Symbol>, ItemType)>,
4748

4849
/// Maps local `DefId`s of exported types to fully qualified paths.
4950
/// Unlike 'paths', this mapping ignores any renames that occur
@@ -55,7 +56,7 @@ crate struct Cache {
5556
/// to the path used if the corresponding type is inlined. By
5657
/// doing this, we can detect duplicate impls on a trait page, and only display
5758
/// the impl for the inlined type.
58-
crate exact_paths: FxHashMap<DefId, Vec<String>>,
59+
crate exact_paths: FxHashMap<DefId, Vec<Symbol>>,
5960

6061
/// This map contains information about all known traits of this crate.
6162
/// Implementations of a crate should inherit the documentation of the
@@ -92,7 +93,7 @@ crate struct Cache {
9293
crate masked_crates: FxHashSet<CrateNum>,
9394

9495
// Private fields only used when initially crawling a crate to build a cache
95-
stack: Vec<String>,
96+
stack: Vec<Symbol>,
9697
parent_stack: Vec<DefId>,
9798
parent_is_trait_impl: bool,
9899
stripped_mod: bool,
@@ -155,7 +156,7 @@ impl Cache {
155156
let dst = &render_options.output;
156157
let location = e.location(extern_url, extern_url_takes_precedence, dst, tcx);
157158
cx.cache.extern_locations.insert(e.crate_num, location);
158-
cx.cache.external_paths.insert(e.def_id(), (vec![name.to_string()], ItemType::Module));
159+
cx.cache.external_paths.insert(e.def_id(), (vec![name], ItemType::Module));
159160
}
160161

161162
// FIXME: avoid this clone (requires implementing Default manually)
@@ -164,10 +165,9 @@ impl Cache {
164165
let crate_name = tcx.crate_name(def_id.krate);
165166
// Recall that we only allow primitive modules to be at the root-level of the crate.
166167
// If that restriction is ever lifted, this will have to include the relative paths instead.
167-
cx.cache.external_paths.insert(
168-
def_id,
169-
(vec![crate_name.to_string(), prim.as_sym().to_string()], ItemType::Primitive),
170-
);
168+
cx.cache
169+
.external_paths
170+
.insert(def_id, (vec![crate_name, prim.as_sym()], ItemType::Primitive));
171171
}
172172

173173
krate = CacheBuilder { tcx, cache: &mut cx.cache }.fold_crate(krate);
@@ -299,7 +299,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
299299
self.cache.search_index.push(IndexItem {
300300
ty: item.type_(),
301301
name: s.to_string(),
302-
path: path.join("::"),
302+
path: join_with_double_colon(path),
303303
desc,
304304
parent,
305305
parent_idx: None,
@@ -320,7 +320,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
320320
// Keep track of the fully qualified path for this item.
321321
let pushed = match item.name {
322322
Some(n) if !n.is_empty() => {
323-
self.cache.stack.push(n.to_string());
323+
self.cache.stack.push(n);
324324
true
325325
}
326326
_ => false,

src/librustdoc/html/format.rs

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rustc_middle::ty;
1919
use rustc_middle::ty::DefIdTree;
2020
use rustc_middle::ty::TyCtxt;
2121
use rustc_span::def_id::CRATE_DEF_INDEX;
22+
use rustc_span::{sym, Symbol};
2223
use rustc_target::spec::abi::Abi;
2324

2425
use crate::clean::{
@@ -502,11 +503,45 @@ crate enum HrefError {
502503
NotInExternalCache,
503504
}
504505

506+
// This mostly works with sequences of symbols, but sometimes the first item
507+
// comes from a string, and in that case we want to trim any trailing `/`.
508+
// `syms` can be empty.
509+
crate fn join_with_slash(first: Option<&str>, syms: &[Symbol]) -> String {
510+
// 64 bytes covers 99.9%+ of cases.
511+
let mut s = String::with_capacity(64);
512+
if let Some(first) = first {
513+
s.push_str(first.trim_end_matches('/'));
514+
if !syms.is_empty() {
515+
s.push('/');
516+
}
517+
}
518+
if !syms.is_empty() {
519+
s.push_str(&syms[0].as_str());
520+
for sym in &syms[1..] {
521+
s.push('/');
522+
s.push_str(&sym.as_str());
523+
}
524+
}
525+
s
526+
}
527+
528+
// Panics if `syms` is empty.
529+
crate fn join_with_double_colon(syms: &[Symbol]) -> String {
530+
// 64 bytes covers 99.9%+ of cases.
531+
let mut s = String::with_capacity(64);
532+
s.push_str(&syms[0].as_str());
533+
for sym in &syms[1..] {
534+
s.push_str("::");
535+
s.push_str(&sym.as_str());
536+
}
537+
s
538+
}
539+
505540
crate fn href_with_root_path(
506541
did: DefId,
507542
cx: &Context<'_>,
508543
root_path: Option<&str>,
509-
) -> Result<(String, ItemType, Vec<String>), HrefError> {
544+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
510545
let tcx = cx.tcx();
511546
let def_kind = tcx.def_kind(did);
512547
let did = match def_kind {
@@ -518,7 +553,7 @@ crate fn href_with_root_path(
518553
};
519554
let cache = cx.cache();
520555
let relative_to = &cx.current;
521-
fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
556+
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
522557
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
523558
}
524559

@@ -533,9 +568,9 @@ crate fn href_with_root_path(
533568
let mut is_remote = false;
534569
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
535570
Some(&(ref fqp, shortty)) => (fqp, shortty, {
536-
let module_fqp = to_module_fqp(shortty, fqp);
571+
let module_fqp = to_module_fqp(shortty, fqp.as_slice());
537572
debug!(?fqp, ?shortty, ?module_fqp);
538-
href_relative_parts(module_fqp, relative_to)
573+
href_relative_parts(module_fqp, relative_to).collect()
539574
}),
540575
None => {
541576
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
@@ -548,10 +583,12 @@ crate fn href_with_root_path(
548583
is_remote = true;
549584
let s = s.trim_end_matches('/');
550585
let mut builder = UrlPartsBuilder::singleton(s);
551-
builder.extend(module_fqp.iter().map(String::as_str));
586+
builder.extend(module_fqp.iter().copied());
552587
builder
553588
}
554-
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
589+
ExternalLocation::Local => {
590+
href_relative_parts(module_fqp, relative_to).collect()
591+
}
555592
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
556593
},
557594
)
@@ -567,45 +604,50 @@ crate fn href_with_root_path(
567604
}
568605
}
569606
debug!(?url_parts);
570-
let last = &fqp.last().unwrap()[..];
571607
match shortty {
572608
ItemType::Module => {
573609
url_parts.push("index.html");
574610
}
575611
_ => {
576-
let filename = format!("{}.{}.html", shortty.as_str(), last);
577-
url_parts.push(&filename);
612+
let prefix = shortty.as_str();
613+
let last = fqp.last().unwrap();
614+
url_parts.push_fmt(format_args!("{}.{}.html", prefix, last));
578615
}
579616
}
580617
Ok((url_parts.finish(), shortty, fqp.to_vec()))
581618
}
582619

583-
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
620+
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
584621
href_with_root_path(did, cx, None)
585622
}
586623

587624
/// Both paths should only be modules.
588625
/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
589626
/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
590-
crate fn href_relative_parts(fqp: &[String], relative_to_fqp: &[String]) -> UrlPartsBuilder {
627+
crate fn href_relative_parts<'fqp>(
628+
fqp: &'fqp [Symbol],
629+
relative_to_fqp: &[Symbol],
630+
) -> Box<dyn Iterator<Item = Symbol> + 'fqp> {
591631
for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
592632
// e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
593633
if f != r {
594634
let dissimilar_part_count = relative_to_fqp.len() - i;
595-
let fqp_module = fqp[i..fqp.len()].iter().map(String::as_str);
596-
return iter::repeat("..").take(dissimilar_part_count).chain(fqp_module).collect();
635+
let fqp_module = &fqp[i..fqp.len()];
636+
return box iter::repeat(sym::dotdot)
637+
.take(dissimilar_part_count)
638+
.chain(fqp_module.iter().copied());
597639
}
598640
}
599641
// e.g. linking to std::sync::atomic from std::sync
600642
if relative_to_fqp.len() < fqp.len() {
601-
fqp[relative_to_fqp.len()..fqp.len()].iter().map(String::as_str).collect()
643+
box fqp[relative_to_fqp.len()..fqp.len()].iter().copied()
602644
// e.g. linking to std::sync from std::sync::atomic
603645
} else if fqp.len() < relative_to_fqp.len() {
604646
let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
605-
iter::repeat("..").take(dissimilar_part_count).collect()
647+
box iter::repeat(sym::dotdot).take(dissimilar_part_count)
606648
// linking to the same module
607649
} else {
608-
UrlPartsBuilder::new()
650+
box iter::empty()
609651
}
610652
}
611653

@@ -632,14 +674,14 @@ fn resolved_path<'cx>(
632674
if let Ok((_, _, fqp)) = href(did, cx) {
633675
format!(
634676
"{}::{}",
635-
fqp[..fqp.len() - 1].join("::"),
636-
anchor(did, fqp.last().unwrap(), cx)
677+
join_with_double_colon(&fqp[..fqp.len() - 1]),
678+
anchor(did, *fqp.last().unwrap(), cx)
637679
)
638680
} else {
639681
last.name.to_string()
640682
}
641683
} else {
642-
anchor(did, last.name.as_str(), cx).to_string()
684+
anchor(did, last.name, cx).to_string()
643685
};
644686
write!(w, "{}{}", path, last.args.print(cx))?;
645687
}
@@ -668,30 +710,31 @@ fn primitive_link(
668710
needs_termination = true;
669711
}
670712
Some(&def_id) => {
671-
let cname_sym;
672713
let loc = match m.extern_locations[&def_id.krate] {
673714
ExternalLocation::Remote(ref s) => {
674-
cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
675-
Some(vec![s.trim_end_matches('/'), cname_sym.as_str()])
715+
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
716+
let builder: UrlPartsBuilder =
717+
[s.as_str().trim_end_matches('/'), cname_sym.as_str()]
718+
.into_iter()
719+
.collect();
720+
Some(builder)
676721
}
677722
ExternalLocation::Local => {
678-
cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
679-
Some(if cx.current.first().map(|x| &x[..]) == Some(cname_sym.as_str()) {
680-
iter::repeat("..").take(cx.current.len() - 1).collect()
723+
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
724+
Some(if cx.current.first() == Some(&cname_sym) {
725+
iter::repeat(sym::dotdot).take(cx.current.len() - 1).collect()
681726
} else {
682-
let cname = iter::once(cname_sym.as_str());
683-
iter::repeat("..").take(cx.current.len()).chain(cname).collect()
727+
iter::repeat(sym::dotdot)
728+
.take(cx.current.len())
729+
.chain(iter::once(cname_sym))
730+
.collect()
684731
})
685732
}
686733
ExternalLocation::Unknown => None,
687734
};
688-
if let Some(loc) = loc {
689-
write!(
690-
f,
691-
"<a class=\"primitive\" href=\"{}/primitive.{}.html\">",
692-
loc.join("/"),
693-
prim.as_sym()
694-
)?;
735+
if let Some(mut loc) = loc {
736+
loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
737+
write!(f, "<a class=\"primitive\" href=\"{}\">", loc.finish())?;
695738
needs_termination = true;
696739
}
697740
}
@@ -730,7 +773,7 @@ fn tybounds<'a, 'tcx: 'a>(
730773

731774
crate fn anchor<'a, 'cx: 'a>(
732775
did: DefId,
733-
text: &'a str,
776+
text: Symbol,
734777
cx: &'cx Context<'_>,
735778
) -> impl fmt::Display + 'a {
736779
let parts = href(did, cx);
@@ -742,8 +785,8 @@ crate fn anchor<'a, 'cx: 'a>(
742785
short_ty,
743786
url,
744787
short_ty,
745-
fqp.join("::"),
746-
text
788+
join_with_double_colon(&fqp),
789+
&*text.as_str()
747790
)
748791
} else {
749792
write!(f, "{}", text)
@@ -960,7 +1003,7 @@ fn fmt_type<'cx>(
9601003
url = url,
9611004
shortty = ItemType::AssocType,
9621005
name = name,
963-
path = path.join("::")
1006+
path = join_with_double_colon(path),
9641007
)?;
9651008
}
9661009
_ => write!(f, "{}", name)?,
@@ -1270,7 +1313,7 @@ impl clean::Visibility {
12701313
debug!("path={:?}", path);
12711314
// modified from `resolved_path()` to work with `DefPathData`
12721315
let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
1273-
let anchor = anchor(vis_did, last_name.as_str(), cx).to_string();
1316+
let anchor = anchor(vis_did, last_name, cx).to_string();
12741317

12751318
let mut s = "pub(in ".to_owned();
12761319
for seg in &path.data[..path.data.len() - 1] {

0 commit comments

Comments
 (0)