Change:
A of Servo's crate shows a huge amount of time (46 seconds!) in name resolution. It turns out that is calling , specifically . That's a lot of unused imports! Sure enough if we create a synthetic file locally with this script: we find: That's a lot of time spent processing unused imports! The slowdown here isn't quite the same as the style crate, unfortunately. The style crate only has ~7k unused imports, which apparently takes it 48s in that profile. In any case the intent here is clear, lots of unused imports seem to slow down compilation, and the crate looks to have a lot of unused imports!
cc
We could address this quickly by just emitting the first 10 unused imports warnings and then warning that there are more unused imports not shown.
I can never deal with more than 10 lints at a time anyway myself, so seems like a reasonable solution to me!
Dumping out a ton of warnings is useful when you're hooked into an IDE and you can see them as e.g. green squiggles under the offending text as opposed to 10k lines on a terminal.
I think the reason is slow is because it calls , which does this: I don't think we want to read source files multiple times like that.
Thank you for looking into Servo compile times Alex, and great find here! Could you CC me or someone from the Servo team for Servo-related issues? Thanks.
Can do!
In general I've always wanted the lint system to avoid even running in blocks. Currently the lint system runs all lints in parallel on the tree. This is great since it means we only traverse it once, and the cost of the traversal is consolidated. We don't need to reduce that cost itself (which is nice because then we don't have to worry about inside ). Most lints do not maintain state. The lint framework has the capability to do so, and we use this a couple times in Clippy, but most lints are , so they won't notice if or whatever isn't called in a region of . The "allowness" of a lint is tied to where in the tree the lint traversal is, not the span emitted by the lint. It's perfectly possible to a block of code and have the lint still be emit there because a different block of code (where it is ) hit the lint but emitted a span it got from elsewhere. We've had trouble with this before in clipyp iirc. All of this enables this proposal: If we had a specialized trait for this, then when we have an we can turn off calling lint methods entirely, and when there's a turn it on again. It doesn't even have to be a trait, really, it could just be a defaulted fake-static method (until happens) on LateLintPass. I suspect this will get significant speedups for crates like Style. I may_ be able to take a crack at this if y'all think this is a good approach.
It occurs to me that a boolean check per lint each tree node might actually end up being more expensive? I'll have look at this code closer. Might be possible to make this fast by having a second array of trait objects which only contains the active lint passes.
It seems like we do a large enough amount of work that the whole "vector of active lints" stuff won't really be necessary. However, I just realized that this bug is not during the lint pass but during the resolution phase (which defers linting to the actual lint pass). This won't be able to help, though we can fix it by doing the spantosnippet in the actual lint and then making this optimization. So in that case I suspect this optimization is unnecessary. In general we've rarely had performance issues with lints in clippy (which has way more lints than rustc), and we've been pretty careful to measure impact there. Still, if folks think this optimization can help, let me know.

Discussion:
use hir::def_id::{CrateNum, DefId, DefIndex, LOCAL_CRATE, DefIndexAddressSpace, CRATE_DEF_INDEX}; use ich::Fingerprint; <del> use rustc_data_structures::fx::{FxHashMap, FxHashSet}; </del> <ins> use rustc_data_structures::fx::FxHashMap; </ins> use rustc_data_structures::indexed_vec::IndexVec; use rustc_data_structures::stable_hasher::StableHasher; use serialize::{Encodable, Decodable, Encoder, Decoder};

pub(super) node_to_hir_id: IndexVec<ast::NodeId, hir::HirId>, macro_def_scopes: FxHashMap<Mark, DefId>, expansions: FxHashMap<DefIndex, Mark>, <del> keys_created: FxHashSet<DefKey>, </del> <ins> next_disambiguator: FxHashMap<(DefIndex, DefPathData), u32>, </ins> } // Unfortunately we have to provide a manual impl of Clone because of the

node_to_hir_id: self.node_to_hir_id.clone(), macro_def_scopes: self.macro_def_scopes.clone(), expansions: self.expansions.clone(), <del> keys_created: self.keys_created.clone(), </del> <ins> next_disambiguator: self.next_disambiguator.clone(), </ins> } } }

node_to_hir_id: IndexVec::new(), macro_def_scopes: FxHashMap(), expansions: FxHashMap(), <del> keys_created: FxHashSet(), </del> <ins> next_disambiguator: FxHashMap(), </ins> } }

// The root node must be created with create_root_def() assert!(data != DefPathData::CrateRoot); <del> // Find a unique DefKey. This basically means incrementing the disambiguator // until we get no match. let mut key = DefKey { </del> <ins> // Find the next free disambiguator for this key. let disambiguator = { let next_disamb = self.next_disambiguator.entry((parent, data.clone())).or_insert(0); let disambiguator = *next_disamb; *next_disamb = next_disamb.checked_add(1).expect("disambiguator overflow"); disambiguator }; let key = DefKey { </ins> parent: Some(parent), disambiguated_data: DisambiguatedDefPathData { <del> data, disambiguator: 0 </del> <ins> data, disambiguator </ins> } }; <del> while self.keys_created.contains(&key) { key.disambiguated_data.disambiguator += 1; } self.keys_created.insert(key.clone()); </del> let parent_hash = self.table.def_path_hash(parent); let def_path_hash = key.compute_stable_hash(parent_hash);

sp } fn ensure_filemap_source_present(&self, file_map: Rc<FileMap>) -> bool { <del> let src = self.file_loader.read_file(Path::new(&file_map.name)).ok(); return file_map.add_external_src(src) </del> <ins> file_map.add_external_src( || self.file_loader.read_file(Path::new(&file_map.name)).ok() ) </ins> } }

/// If the hash of the input doesn't match or no input is supplied via None, /// it is interpreted as an error and the corresponding enum variant is set. /// The return value signifies whether some kind of source is present. <del> pub fn add_external_src(&self, src: Option<String>) -> bool { </del> <ins> pub fn add_external_src<F>(&self, get_src: F) -> bool where F: FnOnce() -> Option<String> { </ins> if *self.external_src.borrow() == ExternalSource::AbsentOk { <ins> let src = get_src(); </ins> let mut external_src = self.external_src.borrow_mut(); if let Some(src) = src { let mut hasher: StableHasher<u128> = StableHasher::new();