1411: add analysis-bench to benchmark incremental analysis r=matklad a=matklad



Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2019-06-16 16:45:30 +00:00
commit 924d4d7ca8
9 changed files with 245 additions and 81 deletions

View File

@ -8,7 +8,7 @@ use ra_db::{
CrateGraph, FileId, SourceRootId, CrateGraph, FileId, SourceRootId,
}; };
use ra_ide_api::{AnalysisHost, AnalysisChange}; use ra_ide_api::{AnalysisHost, AnalysisChange};
use ra_project_model::ProjectWorkspace; use ra_project_model::{ProjectWorkspace, ProjectRoot};
use ra_vfs::{Vfs, VfsChange}; use ra_vfs::{Vfs, VfsChange};
use vfs_filter::IncludeRustFiles; use vfs_filter::IncludeRustFiles;
@ -21,13 +21,11 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
SourceRootId(r.0) SourceRootId(r.0)
} }
pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, Vec<SourceRootId>)> { pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap<SourceRootId, ProjectRoot>)> {
let root = std::env::current_dir()?.join(root); let root = std::env::current_dir()?.join(root);
let ws = ProjectWorkspace::discover(root.as_ref())?; let ws = ProjectWorkspace::discover(root.as_ref())?;
let mut roots = Vec::new(); let project_roots = ws.to_roots();
roots.push(IncludeRustFiles::member(root.clone())); let (mut vfs, roots) = Vfs::new(IncludeRustFiles::from_roots(project_roots.clone()).collect());
roots.extend(IncludeRustFiles::from_roots(ws.to_roots()));
let (mut vfs, roots) = Vfs::new(roots);
let crate_graph = ws.to_crate_graph(&mut |path: &Path| { let crate_graph = ws.to_crate_graph(&mut |path: &Path| {
let vfs_file = vfs.load(path); let vfs_file = vfs.load(path);
log::debug!("vfs file {:?} -> {:?}", path, vfs_file); log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
@ -35,17 +33,27 @@ pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, Vec<SourceRootId>)> {
}); });
log::debug!("crate graph: {:?}", crate_graph); log::debug!("crate graph: {:?}", crate_graph);
let local_roots = roots let source_roots = roots
.into_iter() .iter()
.filter(|r| vfs.root2path(*r).starts_with(&root)) .map(|&vfs_root| {
.map(vfs_root_to_id) let source_root_id = vfs_root_to_id(vfs_root);
.collect(); let project_root = project_roots
.iter()
let host = load(root.as_path(), crate_graph, &mut vfs); .find(|it| it.path() == &vfs.root2path(vfs_root))
Ok((host, local_roots)) .unwrap()
.clone();
(source_root_id, project_root)
})
.collect::<FxHashMap<_, _>>();
let host = load(&source_roots, crate_graph, &mut vfs);
Ok((host, source_roots))
} }
pub fn load(project_root: &Path, crate_graph: CrateGraph, vfs: &mut Vfs) -> AnalysisHost { pub fn load(
source_roots: &FxHashMap<SourceRootId, ProjectRoot>,
crate_graph: CrateGraph,
vfs: &mut Vfs,
) -> AnalysisHost {
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok()); let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
let mut host = AnalysisHost::new(lru_cap); let mut host = AnalysisHost::new(lru_cap);
let mut analysis_change = AnalysisChange::new(); let mut analysis_change = AnalysisChange::new();
@ -60,8 +68,8 @@ pub fn load(project_root: &Path, crate_graph: CrateGraph, vfs: &mut Vfs) -> Anal
for change in vfs.commit_changes() { for change in vfs.commit_changes() {
match change { match change {
VfsChange::AddRoot { root, files } => { VfsChange::AddRoot { root, files } => {
let is_local = vfs.root2path(root).starts_with(&project_root);
let source_root_id = vfs_root_to_id(root); let source_root_id = vfs_root_to_id(root);
let is_local = source_roots[&source_root_id].is_member();
log::debug!( log::debug!(
"loaded source root {:?} with path {:?}", "loaded source root {:?} with path {:?}",
source_root_id, source_root_id,
@ -106,7 +114,7 @@ mod tests {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
let (host, roots) = load_cargo(path).unwrap(); let (host, roots) = load_cargo(path).unwrap();
let mut n_crates = 0; let mut n_crates = 0;
for root in roots { for (root, _) in roots {
for _krate in Crate::source_root_crates(host.raw_database(), root) { for _krate in Crate::source_root_crates(host.raw_database(), root) {
n_crates += 1; n_crates += 1;
} }

View File

@ -40,13 +40,13 @@ impl Filter for IncludeRustFiles {
} }
} }
impl std::convert::From<ProjectRoot> for IncludeRustFiles { impl From<ProjectRoot> for IncludeRustFiles {
fn from(v: ProjectRoot) -> IncludeRustFiles { fn from(v: ProjectRoot) -> IncludeRustFiles {
IncludeRustFiles { root: v } IncludeRustFiles { root: v }
} }
} }
impl std::convert::From<IncludeRustFiles> for RootEntry { impl From<IncludeRustFiles> for RootEntry {
fn from(v: IncludeRustFiles) -> RootEntry { fn from(v: IncludeRustFiles) -> RootEntry {
let path = v.root.path().clone(); let path = v.root.path().clone();
RootEntry::new(path, Box::new(v)) RootEntry::new(path, Box::new(v))

View File

@ -0,0 +1,92 @@
use std::{
path::{PathBuf, Path},
time::Instant,
};
use ra_db::{SourceDatabase, salsa::Database};
use ra_ide_api::{AnalysisHost, Analysis, LineCol, FilePosition};
use crate::Result;
pub(crate) enum Op {
Highlight { path: PathBuf },
Complete { path: PathBuf, line: u32, column: u32 },
}
pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
let start = Instant::now();
eprint!("loading: ");
let (host, roots) = ra_batch::load_cargo(path)?;
let db = host.raw_database();
eprintln!("{:?}\n", start.elapsed());
let file_id = {
let path = match &op {
Op::Highlight { path } => path,
Op::Complete { path, .. } => path,
};
let path = std::env::current_dir()?.join(path).canonicalize()?;
roots
.iter()
.find_map(|(source_root_id, project_root)| {
if project_root.is_member() {
for (rel_path, file_id) in &db.source_root(*source_root_id).files {
let abs_path = rel_path.to_path(project_root.path());
if abs_path == path {
return Some(*file_id);
}
}
}
None
})
.ok_or_else(|| format!("Can't find {:?}", path))?
};
match op {
Op::Highlight { .. } => {
let res = do_work(&host, |analysis| {
analysis.diagnostics(file_id).unwrap();
analysis.highlight_as_html(file_id, false).unwrap()
});
if verbose {
println!("\n{}", res);
}
}
Op::Complete { line, column, .. } => {
let offset = host
.analysis()
.file_line_index(file_id)
.offset(LineCol { line, col_utf16: column });
let file_postion = FilePosition { file_id, offset };
let res = do_work(&host, |analysis| analysis.completions(file_postion));
if verbose {
println!("\n{:#?}", res);
}
}
}
Ok(())
}
fn do_work<F: Fn(&Analysis) -> T, T>(host: &AnalysisHost, work: F) -> T {
{
let start = Instant::now();
eprint!("from scratch: ");
work(&host.analysis());
eprintln!("{:?}", start.elapsed());
}
{
let start = Instant::now();
eprint!("no change: ");
work(&host.analysis());
eprintln!("{:?}", start.elapsed());
}
{
let start = Instant::now();
eprint!("trivial change: ");
host.raw_database().salsa_runtime().next_revision();
let res = work(&host.analysis());
eprintln!("{:?}", start.elapsed());
res
}
}

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, time::Instant, fmt::Write}; use std::{collections::HashSet, time::Instant, fmt::Write, path::Path};
use ra_db::SourceDatabase; use ra_db::SourceDatabase;
use ra_hir::{Crate, ModuleDef, Ty, ImplItem, HasSource}; use ra_hir::{Crate, ModuleDef, Ty, ImplItem, HasSource};
@ -6,22 +6,25 @@ use ra_syntax::AstNode;
use crate::Result; use crate::Result;
pub fn run(verbose: bool, path: &str, only: Option<&str>) -> Result<()> { pub fn run(verbose: bool, path: &Path, only: Option<&str>) -> Result<()> {
let db_load_time = Instant::now(); let db_load_time = Instant::now();
let (host, roots) = ra_batch::load_cargo(path.as_ref())?; let (host, roots) = ra_batch::load_cargo(path)?;
let db = host.raw_database(); let db = host.raw_database();
println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
let analysis_time = Instant::now(); let analysis_time = Instant::now();
let mut num_crates = 0; let mut num_crates = 0;
let mut visited_modules = HashSet::new(); let mut visited_modules = HashSet::new();
let mut visit_queue = Vec::new(); let mut visit_queue = Vec::new();
for root in roots { for (source_root_id, project_root) in roots {
for krate in Crate::source_root_crates(db, root) { if project_root.is_member() {
for krate in Crate::source_root_crates(db, source_root_id) {
num_crates += 1; num_crates += 1;
let module = krate.root_module(db).expect("crate in source root without root module"); let module =
krate.root_module(db).expect("crate in source root without root module");
visit_queue.push(module); visit_queue.push(module);
} }
} }
}
println!("Crates in this dir: {}", num_crates); println!("Crates in this dir: {}", num_crates);
let mut num_decls = 0; let mut num_decls = 0;
let mut funcs = Vec::new(); let mut funcs = Vec::new();

View File

@ -1,4 +1,5 @@
mod analysis_stats; mod analysis_stats;
mod analysis_bench;
use std::{io::Read, error::Error}; use std::{io::Read, error::Error};
@ -26,6 +27,27 @@ fn main() -> Result<()> {
.arg(Arg::with_name("only").short("o").takes_value(true)) .arg(Arg::with_name("only").short("o").takes_value(true))
.arg(Arg::with_name("path")), .arg(Arg::with_name("path")),
) )
.subcommand(
SubCommand::with_name("analysis-bench")
.arg(Arg::with_name("verbose").short("v").long("verbose"))
.arg(
Arg::with_name("highlight")
.long("highlight")
.takes_value(true)
.conflicts_with("complete")
.value_name("PATH")
.help("highlight this file"),
)
.arg(
Arg::with_name("complete")
.long("complete")
.takes_value(true)
.conflicts_with("highlight")
.value_name("PATH:LINE:COLUMN")
.help("compute completions at this location"),
)
.arg(Arg::with_name("path").value_name("PATH").help("project to analyze")),
)
.get_matches(); .get_matches();
match matches.subcommand() { match matches.subcommand() {
("parse", Some(matches)) => { ("parse", Some(matches)) => {
@ -51,7 +73,25 @@ fn main() -> Result<()> {
let verbose = matches.is_present("verbose"); let verbose = matches.is_present("verbose");
let path = matches.value_of("path").unwrap_or(""); let path = matches.value_of("path").unwrap_or("");
let only = matches.value_of("only"); let only = matches.value_of("only");
analysis_stats::run(verbose, path, only)?; analysis_stats::run(verbose, path.as_ref(), only)?;
}
("analysis-bench", Some(matches)) => {
let verbose = matches.is_present("verbose");
let path = matches.value_of("path").unwrap_or("");
let op = if let Some(path) = matches.value_of("highlight") {
analysis_bench::Op::Highlight { path: path.into() }
} else if let Some(path_line_col) = matches.value_of("complete") {
let (path_line, column) = rsplit_at_char(path_line_col, ':')?;
let (path, line) = rsplit_at_char(path_line, ':')?;
analysis_bench::Op::Complete {
path: path.into(),
line: line.parse()?,
column: column.parse()?,
}
} else {
panic!("either --highlight or --complete must be set")
};
analysis_bench::run(verbose, path.as_ref(), op)?;
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -68,3 +108,8 @@ fn read_stdin() -> Result<String> {
std::io::stdin().read_to_string(&mut buff)?; std::io::stdin().read_to_string(&mut buff)?;
Ok(buff) Ok(buff)
} }
fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
let idx = s.rfind(":").ok_or_else(|| format!("no `{}` in {}", c, s))?;
Ok((&s[..idx], &s[idx + 1..]))
}

View File

@ -276,7 +276,7 @@ impl AnalysisHost {
pub fn collect_garbage(&mut self) { pub fn collect_garbage(&mut self) {
self.db.collect_garbage(); self.db.collect_garbage();
} }
pub fn raw_database(&self) -> &impl hir::db::HirDatabase { pub fn raw_database(&self) -> &(impl hir::db::HirDatabase + salsa::Database) {
&self.db &self.db
} }
} }

View File

@ -10,7 +10,9 @@ pub struct LineIndex {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LineCol { pub struct LineCol {
/// Zero-based
pub line: u32, pub line: u32,
/// Zero-based
pub col_utf16: u32, pub col_utf16: u32,
} }

View File

@ -37,6 +37,7 @@ pub enum ProjectWorkspace {
/// `ProjectRoot` describes a workspace root folder. /// `ProjectRoot` describes a workspace root folder.
/// Which may be an external dependency, or a member of /// Which may be an external dependency, or a member of
/// the current workspace. /// the current workspace.
#[derive(Clone)]
pub struct ProjectRoot { pub struct ProjectRoot {
/// Path to the root folder /// Path to the root folder
path: PathBuf, path: PathBuf,

View File

@ -147,3 +147,16 @@ RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more tha
``` ```
In particular, I have `export RA_PROFILE='*>10' in my shell profile. In particular, I have `export RA_PROFILE='*>10' in my shell profile.
To measure time for from-scratch analysis, use something like this:
```
$ cargo run --release -p ra_cli -- analysis-stats ../chalk/
```
For measuring time of incremental analysis, use either of these:
```
$ cargo run --release -p ra_cli -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs
$ cargo run --release -p ra_cli -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0
```