Skip to content

Commit 924d4d7

Browse files
bors[bot]matklad
andcommitted
Merge #1411
1411: add analysis-bench to benchmark incremental analysis r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
2 parents b81caed + 6314e62 commit 924d4d7

File tree

9 files changed

+245
-81
lines changed

9 files changed

+245
-81
lines changed

crates/ra_batch/src/lib.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use ra_db::{
88
CrateGraph, FileId, SourceRootId,
99
};
1010
use ra_ide_api::{AnalysisHost, AnalysisChange};
11-
use ra_project_model::ProjectWorkspace;
11+
use ra_project_model::{ProjectWorkspace, ProjectRoot};
1212
use ra_vfs::{Vfs, VfsChange};
1313
use vfs_filter::IncludeRustFiles;
1414

@@ -21,31 +21,39 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
2121
SourceRootId(r.0)
2222
}
2323

24-
pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, Vec<SourceRootId>)> {
24+
pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap<SourceRootId, ProjectRoot>)> {
2525
let root = std::env::current_dir()?.join(root);
2626
let ws = ProjectWorkspace::discover(root.as_ref())?;
27-
let mut roots = Vec::new();
28-
roots.push(IncludeRustFiles::member(root.clone()));
29-
roots.extend(IncludeRustFiles::from_roots(ws.to_roots()));
30-
let (mut vfs, roots) = Vfs::new(roots);
27+
let project_roots = ws.to_roots();
28+
let (mut vfs, roots) = Vfs::new(IncludeRustFiles::from_roots(project_roots.clone()).collect());
3129
let crate_graph = ws.to_crate_graph(&mut |path: &Path| {
3230
let vfs_file = vfs.load(path);
3331
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
3432
vfs_file.map(vfs_file_to_id)
3533
});
3634
log::debug!("crate graph: {:?}", crate_graph);
3735

38-
let local_roots = roots
39-
.into_iter()
40-
.filter(|r| vfs.root2path(*r).starts_with(&root))
41-
.map(vfs_root_to_id)
42-
.collect();
43-
44-
let host = load(root.as_path(), crate_graph, &mut vfs);
45-
Ok((host, local_roots))
36+
let source_roots = roots
37+
.iter()
38+
.map(|&vfs_root| {
39+
let source_root_id = vfs_root_to_id(vfs_root);
40+
let project_root = project_roots
41+
.iter()
42+
.find(|it| it.path() == &vfs.root2path(vfs_root))
43+
.unwrap()
44+
.clone();
45+
(source_root_id, project_root)
46+
})
47+
.collect::<FxHashMap<_, _>>();
48+
let host = load(&source_roots, crate_graph, &mut vfs);
49+
Ok((host, source_roots))
4650
}
4751

48-
pub fn load(project_root: &Path, crate_graph: CrateGraph, vfs: &mut Vfs) -> AnalysisHost {
52+
pub fn load(
53+
source_roots: &FxHashMap<SourceRootId, ProjectRoot>,
54+
crate_graph: CrateGraph,
55+
vfs: &mut Vfs,
56+
) -> AnalysisHost {
4957
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
5058
let mut host = AnalysisHost::new(lru_cap);
5159
let mut analysis_change = AnalysisChange::new();
@@ -60,8 +68,8 @@ pub fn load(project_root: &Path, crate_graph: CrateGraph, vfs: &mut Vfs) -> Anal
6068
for change in vfs.commit_changes() {
6169
match change {
6270
VfsChange::AddRoot { root, files } => {
63-
let is_local = vfs.root2path(root).starts_with(&project_root);
6471
let source_root_id = vfs_root_to_id(root);
72+
let is_local = source_roots[&source_root_id].is_member();
6573
log::debug!(
6674
"loaded source root {:?} with path {:?}",
6775
source_root_id,
@@ -106,7 +114,7 @@ mod tests {
106114
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
107115
let (host, roots) = load_cargo(path).unwrap();
108116
let mut n_crates = 0;
109-
for root in roots {
117+
for (root, _) in roots {
110118
for _krate in Crate::source_root_crates(host.raw_database(), root) {
111119
n_crates += 1;
112120
}

crates/ra_batch/src/vfs_filter.rs

Lines changed: 54 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,54 @@
1-
use std::path::PathBuf;
2-
use ra_project_model::ProjectRoot;
3-
use ra_vfs::{RootEntry, Filter, RelativePath};
4-
5-
/// `IncludeRustFiles` is used to convert
6-
/// from `ProjectRoot` to `RootEntry` for VFS
7-
pub struct IncludeRustFiles {
8-
root: ProjectRoot,
9-
}
10-
11-
impl IncludeRustFiles {
12-
pub fn from_roots<R>(roots: R) -> impl Iterator<Item = RootEntry>
13-
where
14-
R: IntoIterator<Item = ProjectRoot>,
15-
{
16-
roots.into_iter().map(IncludeRustFiles::from_root)
17-
}
18-
19-
pub fn from_root(root: ProjectRoot) -> RootEntry {
20-
IncludeRustFiles::from(root).into()
21-
}
22-
23-
#[allow(unused)]
24-
pub fn external(path: PathBuf) -> RootEntry {
25-
IncludeRustFiles::from_root(ProjectRoot::new(path, false))
26-
}
27-
28-
pub fn member(path: PathBuf) -> RootEntry {
29-
IncludeRustFiles::from_root(ProjectRoot::new(path, true))
30-
}
31-
}
32-
33-
impl Filter for IncludeRustFiles {
34-
fn include_dir(&self, dir_path: &RelativePath) -> bool {
35-
self.root.include_dir(dir_path)
36-
}
37-
38-
fn include_file(&self, file_path: &RelativePath) -> bool {
39-
self.root.include_file(file_path)
40-
}
41-
}
42-
43-
impl std::convert::From<ProjectRoot> for IncludeRustFiles {
44-
fn from(v: ProjectRoot) -> IncludeRustFiles {
45-
IncludeRustFiles { root: v }
46-
}
47-
}
48-
49-
impl std::convert::From<IncludeRustFiles> for RootEntry {
50-
fn from(v: IncludeRustFiles) -> RootEntry {
51-
let path = v.root.path().clone();
52-
RootEntry::new(path, Box::new(v))
53-
}
54-
}
1+
use std::path::PathBuf;
2+
use ra_project_model::ProjectRoot;
3+
use ra_vfs::{RootEntry, Filter, RelativePath};
4+
5+
/// `IncludeRustFiles` is used to convert
6+
/// from `ProjectRoot` to `RootEntry` for VFS
7+
pub struct IncludeRustFiles {
8+
root: ProjectRoot,
9+
}
10+
11+
impl IncludeRustFiles {
12+
pub fn from_roots<R>(roots: R) -> impl Iterator<Item = RootEntry>
13+
where
14+
R: IntoIterator<Item = ProjectRoot>,
15+
{
16+
roots.into_iter().map(IncludeRustFiles::from_root)
17+
}
18+
19+
pub fn from_root(root: ProjectRoot) -> RootEntry {
20+
IncludeRustFiles::from(root).into()
21+
}
22+
23+
#[allow(unused)]
24+
pub fn external(path: PathBuf) -> RootEntry {
25+
IncludeRustFiles::from_root(ProjectRoot::new(path, false))
26+
}
27+
28+
pub fn member(path: PathBuf) -> RootEntry {
29+
IncludeRustFiles::from_root(ProjectRoot::new(path, true))
30+
}
31+
}
32+
33+
impl Filter for IncludeRustFiles {
34+
fn include_dir(&self, dir_path: &RelativePath) -> bool {
35+
self.root.include_dir(dir_path)
36+
}
37+
38+
fn include_file(&self, file_path: &RelativePath) -> bool {
39+
self.root.include_file(file_path)
40+
}
41+
}
42+
43+
impl From<ProjectRoot> for IncludeRustFiles {
44+
fn from(v: ProjectRoot) -> IncludeRustFiles {
45+
IncludeRustFiles { root: v }
46+
}
47+
}
48+
49+
impl From<IncludeRustFiles> for RootEntry {
50+
fn from(v: IncludeRustFiles) -> RootEntry {
51+
let path = v.root.path().clone();
52+
RootEntry::new(path, Box::new(v))
53+
}
54+
}

crates/ra_cli/src/analysis_bench.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::{
2+
path::{PathBuf, Path},
3+
time::Instant,
4+
};
5+
6+
use ra_db::{SourceDatabase, salsa::Database};
7+
use ra_ide_api::{AnalysisHost, Analysis, LineCol, FilePosition};
8+
9+
use crate::Result;
10+
11+
pub(crate) enum Op {
12+
Highlight { path: PathBuf },
13+
Complete { path: PathBuf, line: u32, column: u32 },
14+
}
15+
16+
pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
17+
let start = Instant::now();
18+
eprint!("loading: ");
19+
let (host, roots) = ra_batch::load_cargo(path)?;
20+
let db = host.raw_database();
21+
eprintln!("{:?}\n", start.elapsed());
22+
23+
let file_id = {
24+
let path = match &op {
25+
Op::Highlight { path } => path,
26+
Op::Complete { path, .. } => path,
27+
};
28+
let path = std::env::current_dir()?.join(path).canonicalize()?;
29+
roots
30+
.iter()
31+
.find_map(|(source_root_id, project_root)| {
32+
if project_root.is_member() {
33+
for (rel_path, file_id) in &db.source_root(*source_root_id).files {
34+
let abs_path = rel_path.to_path(project_root.path());
35+
if abs_path == path {
36+
return Some(*file_id);
37+
}
38+
}
39+
}
40+
None
41+
})
42+
.ok_or_else(|| format!("Can't find {:?}", path))?
43+
};
44+
45+
match op {
46+
Op::Highlight { .. } => {
47+
let res = do_work(&host, |analysis| {
48+
analysis.diagnostics(file_id).unwrap();
49+
analysis.highlight_as_html(file_id, false).unwrap()
50+
});
51+
if verbose {
52+
println!("\n{}", res);
53+
}
54+
}
55+
Op::Complete { line, column, .. } => {
56+
let offset = host
57+
.analysis()
58+
.file_line_index(file_id)
59+
.offset(LineCol { line, col_utf16: column });
60+
let file_postion = FilePosition { file_id, offset };
61+
62+
let res = do_work(&host, |analysis| analysis.completions(file_postion));
63+
if verbose {
64+
println!("\n{:#?}", res);
65+
}
66+
}
67+
}
68+
Ok(())
69+
}
70+
71+
fn do_work<F: Fn(&Analysis) -> T, T>(host: &AnalysisHost, work: F) -> T {
72+
{
73+
let start = Instant::now();
74+
eprint!("from scratch: ");
75+
work(&host.analysis());
76+
eprintln!("{:?}", start.elapsed());
77+
}
78+
{
79+
let start = Instant::now();
80+
eprint!("no change: ");
81+
work(&host.analysis());
82+
eprintln!("{:?}", start.elapsed());
83+
}
84+
{
85+
let start = Instant::now();
86+
eprint!("trivial change: ");
87+
host.raw_database().salsa_runtime().next_revision();
88+
let res = work(&host.analysis());
89+
eprintln!("{:?}", start.elapsed());
90+
res
91+
}
92+
}

crates/ra_cli/src/analysis_stats.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
1-
use std::{collections::HashSet, time::Instant, fmt::Write};
1+
use std::{collections::HashSet, time::Instant, fmt::Write, path::Path};
22

33
use ra_db::SourceDatabase;
44
use ra_hir::{Crate, ModuleDef, Ty, ImplItem, HasSource};
55
use ra_syntax::AstNode;
66

77
use crate::Result;
88

9-
pub fn run(verbose: bool, path: &str, only: Option<&str>) -> Result<()> {
9+
pub fn run(verbose: bool, path: &Path, only: Option<&str>) -> Result<()> {
1010
let db_load_time = Instant::now();
11-
let (host, roots) = ra_batch::load_cargo(path.as_ref())?;
11+
let (host, roots) = ra_batch::load_cargo(path)?;
1212
let db = host.raw_database();
1313
println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
1414
let analysis_time = Instant::now();
1515
let mut num_crates = 0;
1616
let mut visited_modules = HashSet::new();
1717
let mut visit_queue = Vec::new();
18-
for root in roots {
19-
for krate in Crate::source_root_crates(db, root) {
20-
num_crates += 1;
21-
let module = krate.root_module(db).expect("crate in source root without root module");
22-
visit_queue.push(module);
18+
for (source_root_id, project_root) in roots {
19+
if project_root.is_member() {
20+
for krate in Crate::source_root_crates(db, source_root_id) {
21+
num_crates += 1;
22+
let module =
23+
krate.root_module(db).expect("crate in source root without root module");
24+
visit_queue.push(module);
25+
}
2326
}
2427
}
2528
println!("Crates in this dir: {}", num_crates);

crates/ra_cli/src/main.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod analysis_stats;
2+
mod analysis_bench;
23

34
use std::{io::Read, error::Error};
45

@@ -26,6 +27,27 @@ fn main() -> Result<()> {
2627
.arg(Arg::with_name("only").short("o").takes_value(true))
2728
.arg(Arg::with_name("path")),
2829
)
30+
.subcommand(
31+
SubCommand::with_name("analysis-bench")
32+
.arg(Arg::with_name("verbose").short("v").long("verbose"))
33+
.arg(
34+
Arg::with_name("highlight")
35+
.long("highlight")
36+
.takes_value(true)
37+
.conflicts_with("complete")
38+
.value_name("PATH")
39+
.help("highlight this file"),
40+
)
41+
.arg(
42+
Arg::with_name("complete")
43+
.long("complete")
44+
.takes_value(true)
45+
.conflicts_with("highlight")
46+
.value_name("PATH:LINE:COLUMN")
47+
.help("compute completions at this location"),
48+
)
49+
.arg(Arg::with_name("path").value_name("PATH").help("project to analyze")),
50+
)
2951
.get_matches();
3052
match matches.subcommand() {
3153
("parse", Some(matches)) => {
@@ -51,7 +73,25 @@ fn main() -> Result<()> {
5173
let verbose = matches.is_present("verbose");
5274
let path = matches.value_of("path").unwrap_or("");
5375
let only = matches.value_of("only");
54-
analysis_stats::run(verbose, path, only)?;
76+
analysis_stats::run(verbose, path.as_ref(), only)?;
77+
}
78+
("analysis-bench", Some(matches)) => {
79+
let verbose = matches.is_present("verbose");
80+
let path = matches.value_of("path").unwrap_or("");
81+
let op = if let Some(path) = matches.value_of("highlight") {
82+
analysis_bench::Op::Highlight { path: path.into() }
83+
} else if let Some(path_line_col) = matches.value_of("complete") {
84+
let (path_line, column) = rsplit_at_char(path_line_col, ':')?;
85+
let (path, line) = rsplit_at_char(path_line, ':')?;
86+
analysis_bench::Op::Complete {
87+
path: path.into(),
88+
line: line.parse()?,
89+
column: column.parse()?,
90+
}
91+
} else {
92+
panic!("either --highlight or --complete must be set")
93+
};
94+
analysis_bench::run(verbose, path.as_ref(), op)?;
5595
}
5696
_ => unreachable!(),
5797
}
@@ -68,3 +108,8 @@ fn read_stdin() -> Result<String> {
68108
std::io::stdin().read_to_string(&mut buff)?;
69109
Ok(buff)
70110
}
111+
112+
fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
113+
let idx = s.rfind(":").ok_or_else(|| format!("no `{}` in {}", c, s))?;
114+
Ok((&s[..idx], &s[idx + 1..]))
115+
}

crates/ra_ide_api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ impl AnalysisHost {
276276
pub fn collect_garbage(&mut self) {
277277
self.db.collect_garbage();
278278
}
279-
pub fn raw_database(&self) -> &impl hir::db::HirDatabase {
279+
pub fn raw_database(&self) -> &(impl hir::db::HirDatabase + salsa::Database) {
280280
&self.db
281281
}
282282
}

0 commit comments

Comments
 (0)