From 6314e62cfb06ea7bbe5f530f2824010be0ffa4c7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 16 Jun 2019 19:19:38 +0300 Subject: add analysis-bench to benchmark incremental analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Can be used like this: ``` $ cargo run --release -p ra_cli -- \ analysis-bench ../chalk/ \ --complete ../chalk/chalk-engine/src/logic.rs:94:0 loading: 225.970093ms from scratch: 8.492373325s no change: 445.265µs trivial change: 95.631242ms ``` Or like this: ``` $ cargo run --release -p ra_cli -- \ analysis-bench ../chalk/ \ --highlight ../chalk/chalk-engine/src/logic.rs loading: 209.873484ms from scratch: 9.504916942s no change: 7.731119ms trivial change: 124.984039ms ``` "from scratch" includes initial analysis of the relevant bits of the project "no change" just asks the same question for the second time. It measures overhead on assembling the answer outside of salsa. "trivial change" doesn't do an actual salsa change, it just advances the revision. This test how fast is salsa at validating things. --- crates/ra_cli/src/analysis_bench.rs | 92 +++++++++++++++++++++++++++++++++++++ crates/ra_cli/src/analysis_stats.rs | 19 ++++---- crates/ra_cli/src/main.rs | 47 ++++++++++++++++++- 3 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 crates/ra_cli/src/analysis_bench.rs (limited to 'crates/ra_cli/src') diff --git a/crates/ra_cli/src/analysis_bench.rs b/crates/ra_cli/src/analysis_bench.rs new file mode 100644 index 000000000..33d472838 --- /dev/null +++ b/crates/ra_cli/src/analysis_bench.rs @@ -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 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 + } +} diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs index d76c37d84..ed98fc7f6 100644 --- a/crates/ra_cli/src/analysis_stats.rs +++ b/crates/ra_cli/src/analysis_stats.rs @@ -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_hir::{Crate, ModuleDef, Ty, ImplItem, HasSource}; @@ -6,20 +6,23 @@ use ra_syntax::AstNode; 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 (host, roots) = ra_batch::load_cargo(path.as_ref())?; + let (host, roots) = ra_batch::load_cargo(path)?; let db = host.raw_database(); println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); let analysis_time = Instant::now(); let mut num_crates = 0; let mut visited_modules = HashSet::new(); let mut visit_queue = Vec::new(); - for root in roots { - for krate in Crate::source_root_crates(db, root) { - num_crates += 1; - let module = krate.root_module(db).expect("crate in source root without root module"); - visit_queue.push(module); + for (source_root_id, project_root) in roots { + if project_root.is_member() { + for krate in Crate::source_root_crates(db, source_root_id) { + num_crates += 1; + let module = + krate.root_module(db).expect("crate in source root without root module"); + visit_queue.push(module); + } } } println!("Crates in this dir: {}", num_crates); diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index 1db98aec1..5adf8b096 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -1,4 +1,5 @@ mod analysis_stats; +mod analysis_bench; 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("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(); match matches.subcommand() { ("parse", Some(matches)) => { @@ -51,7 +73,25 @@ fn main() -> Result<()> { let verbose = matches.is_present("verbose"); let path = matches.value_of("path").unwrap_or(""); 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!(), } @@ -68,3 +108,8 @@ fn read_stdin() -> Result { std::io::stdin().read_to_string(&mut 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..])) +} -- cgit v1.2.3