diff options
author | Aleksey Kladov <[email protected]> | 2021-03-30 09:59:00 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-03-30 10:47:23 +0100 |
commit | 797185e1b66fb0d6ec1dedf206616890b5e3fef3 (patch) | |
tree | 8455d51018e4c395ac33d6df4174c94dbd6dbcb1 | |
parent | 904bdff2241a54c6aebe0f6fd39e6560550a66b0 (diff) |
internal: switch from CLI to internal benchmarking
We have a CLI for benchmarking, but no one actually uses it it seems.
Let's try switching to "internal" benchmarks, implemented as rust tests.
They should be easier to "script" to automate tracking of perf
regressions.
-rw-r--r-- | crates/rust-analyzer/src/benchmarks.rs | 69 | ||||
-rw-r--r-- | crates/rust-analyzer/src/bin/flags.rs | 53 | ||||
-rw-r--r-- | crates/rust-analyzer/src/bin/main.rs | 13 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/analysis_bench.rs | 196 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lib.rs | 3 |
6 files changed, 76 insertions, 262 deletions
diff --git a/crates/rust-analyzer/src/benchmarks.rs b/crates/rust-analyzer/src/benchmarks.rs new file mode 100644 index 000000000..a6f997af8 --- /dev/null +++ b/crates/rust-analyzer/src/benchmarks.rs | |||
@@ -0,0 +1,69 @@ | |||
1 | //! Fully integrated benchmarks for rust-analyzer, which load real cargo | ||
2 | //! projects. | ||
3 | //! | ||
4 | //! The benchmark here is used to debug specific performance regressions. If you | ||
5 | //! notice that, eg, completion is slow in some specific case, you can modify | ||
6 | //! code here exercise this specific completion, and thus have a fast | ||
7 | //! edit/compile/test cycle. | ||
8 | //! | ||
9 | //! Note that "Rust Analyzer: Run" action does not allow running a single test | ||
10 | //! in release mode in VS Code. There's however "Rust Analyzer: Copy Run Command Line" | ||
11 | //! which you can use to paste the command in terminal and add `--release` manually. | ||
12 | |||
13 | use std::sync::Arc; | ||
14 | |||
15 | use ide::Change; | ||
16 | use test_utils::project_root; | ||
17 | use vfs::{AbsPathBuf, VfsPath}; | ||
18 | |||
19 | use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig}; | ||
20 | |||
21 | #[test] | ||
22 | fn benchmark_integrated_highlighting() { | ||
23 | // Don't run slow benchmark by default | ||
24 | if true { | ||
25 | return; | ||
26 | } | ||
27 | |||
28 | // Load rust-analyzer itself. | ||
29 | let workspace_to_load = project_root(); | ||
30 | let file = "./crates/ide_db/src/apply_change.rs"; | ||
31 | |||
32 | let cargo_config = Default::default(); | ||
33 | let load_cargo_config = | ||
34 | LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro: false }; | ||
35 | |||
36 | let (mut host, vfs, _proc_macro) = { | ||
37 | let _it = stdx::timeit("workspace loading"); | ||
38 | load_workspace_at(&workspace_to_load, &cargo_config, &load_cargo_config, &|_| {}).unwrap() | ||
39 | }; | ||
40 | |||
41 | let file_id = { | ||
42 | let file = workspace_to_load.join(file); | ||
43 | let path = VfsPath::from(AbsPathBuf::assert(file)); | ||
44 | vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {}", path)) | ||
45 | }; | ||
46 | |||
47 | { | ||
48 | let _it = stdx::timeit("initial"); | ||
49 | let analysis = host.analysis(); | ||
50 | analysis.highlight_as_html(file_id, false).unwrap(); | ||
51 | } | ||
52 | |||
53 | profile::init_from("*>100"); | ||
54 | |||
55 | { | ||
56 | let _it = stdx::timeit("change"); | ||
57 | let mut text = host.analysis().file_text(file_id).unwrap().to_string(); | ||
58 | text.push_str("\npub fn _dummy() {}\n"); | ||
59 | let mut change = Change::new(); | ||
60 | change.change_file(file_id, Some(Arc::new(text))); | ||
61 | host.apply_change(change); | ||
62 | } | ||
63 | |||
64 | { | ||
65 | let _it = stdx::timeit("after change"); | ||
66 | let analysis = host.analysis(); | ||
67 | analysis.highlight_as_html(file_id, false).unwrap(); | ||
68 | } | ||
69 | } | ||
diff --git a/crates/rust-analyzer/src/bin/flags.rs b/crates/rust-analyzer/src/bin/flags.rs index d8987633d..b05fc00b9 100644 --- a/crates/rust-analyzer/src/bin/flags.rs +++ b/crates/rust-analyzer/src/bin/flags.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | //! Grammar for the command-line arguments. | 1 | //! Grammar for the command-line arguments. |
2 | #![allow(unreachable_pub)] | 2 | #![allow(unreachable_pub)] |
3 | use std::{env, path::PathBuf}; | 3 | use std::path::PathBuf; |
4 | 4 | ||
5 | use ide_ssr::{SsrPattern, SsrRule}; | 5 | use ide_ssr::{SsrPattern, SsrRule}; |
6 | use rust_analyzer::cli::{BenchWhat, Position, Verbosity}; | 6 | use rust_analyzer::cli::Verbosity; |
7 | use vfs::AbsPathBuf; | ||
8 | 7 | ||
9 | xflags::xflags! { | 8 | xflags::xflags! { |
10 | src "./src/bin/flags.rs" | 9 | src "./src/bin/flags.rs" |
@@ -74,27 +73,6 @@ xflags::xflags! { | |||
74 | optional --with-proc-macro | 73 | optional --with-proc-macro |
75 | } | 74 | } |
76 | 75 | ||
77 | /// Benchmark specific analysis operation | ||
78 | cmd analysis-bench | ||
79 | /// Directory with Cargo.toml. | ||
80 | required path: PathBuf | ||
81 | { | ||
82 | /// Collect memory usage statistics. | ||
83 | optional --memory-usage | ||
84 | |||
85 | /// Compute syntax highlighting for this file | ||
86 | optional --highlight path: PathBuf | ||
87 | /// Compute completions at file:line:column location. | ||
88 | optional --complete location: Position | ||
89 | /// Compute goto definition at file:line:column location. | ||
90 | optional --goto-def location: Position | ||
91 | |||
92 | /// Load OUT_DIR values by running `cargo check` before analysis. | ||
93 | optional --load-output-dirs | ||
94 | /// Use proc-macro-srv for proc-macro expanding. | ||
95 | optional --with-proc-macro | ||
96 | } | ||
97 | |||
98 | cmd diagnostics | 76 | cmd diagnostics |
99 | /// Directory with Cargo.toml. | 77 | /// Directory with Cargo.toml. |
100 | required path: PathBuf | 78 | required path: PathBuf |
@@ -142,7 +120,6 @@ pub enum RustAnalyzerCmd { | |||
142 | Symbols(Symbols), | 120 | Symbols(Symbols), |
143 | Highlight(Highlight), | 121 | Highlight(Highlight), |
144 | AnalysisStats(AnalysisStats), | 122 | AnalysisStats(AnalysisStats), |
145 | AnalysisBench(AnalysisBench), | ||
146 | Diagnostics(Diagnostics), | 123 | Diagnostics(Diagnostics), |
147 | Ssr(Ssr), | 124 | Ssr(Ssr), |
148 | Search(Search), | 125 | Search(Search), |
@@ -184,18 +161,6 @@ pub struct AnalysisStats { | |||
184 | } | 161 | } |
185 | 162 | ||
186 | #[derive(Debug)] | 163 | #[derive(Debug)] |
187 | pub struct AnalysisBench { | ||
188 | pub path: PathBuf, | ||
189 | |||
190 | pub memory_usage: bool, | ||
191 | pub highlight: Option<PathBuf>, | ||
192 | pub complete: Option<Position>, | ||
193 | pub goto_def: Option<Position>, | ||
194 | pub load_output_dirs: bool, | ||
195 | pub with_proc_macro: bool, | ||
196 | } | ||
197 | |||
198 | #[derive(Debug)] | ||
199 | pub struct Diagnostics { | 164 | pub struct Diagnostics { |
200 | pub path: PathBuf, | 165 | pub path: PathBuf, |
201 | 166 | ||
@@ -239,17 +204,3 @@ impl RustAnalyzer { | |||
239 | } | 204 | } |
240 | } | 205 | } |
241 | } | 206 | } |
242 | |||
243 | impl AnalysisBench { | ||
244 | pub(crate) fn what(&self) -> BenchWhat { | ||
245 | match (&self.highlight, &self.complete, &self.goto_def) { | ||
246 | (Some(path), None, None) => { | ||
247 | let path = env::current_dir().unwrap().join(path); | ||
248 | BenchWhat::Highlight { path: AbsPathBuf::assert(path) } | ||
249 | } | ||
250 | (None, Some(position), None) => BenchWhat::Complete(position.clone()), | ||
251 | (None, None, Some(position)) => BenchWhat::GotoDef(position.clone()), | ||
252 | _ => panic!("exactly one of `--highlight`, `--complete` or `--goto-def` must be set"), | ||
253 | } | ||
254 | } | ||
255 | } | ||
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index a0b611bff..ae99eefe3 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs | |||
@@ -9,7 +9,7 @@ use std::{convert::TryFrom, env, fs, path::Path, process}; | |||
9 | use lsp_server::Connection; | 9 | use lsp_server::Connection; |
10 | use project_model::ProjectManifest; | 10 | use project_model::ProjectManifest; |
11 | use rust_analyzer::{ | 11 | use rust_analyzer::{ |
12 | cli::{self, AnalysisStatsCmd, BenchCmd}, | 12 | cli::{self, AnalysisStatsCmd}, |
13 | config::Config, | 13 | config::Config, |
14 | from_json, | 14 | from_json, |
15 | lsp_ext::supports_utf8, | 15 | lsp_ext::supports_utf8, |
@@ -80,17 +80,6 @@ fn try_main() -> Result<()> { | |||
80 | with_proc_macro: cmd.with_proc_macro, | 80 | with_proc_macro: cmd.with_proc_macro, |
81 | } | 81 | } |
82 | .run(verbosity)?, | 82 | .run(verbosity)?, |
83 | flags::RustAnalyzerCmd::AnalysisBench(cmd) => { | ||
84 | let what = cmd.what(); | ||
85 | BenchCmd { | ||
86 | memory_usage: cmd.memory_usage, | ||
87 | path: cmd.path, | ||
88 | load_output_dirs: cmd.load_output_dirs, | ||
89 | with_proc_macro: cmd.with_proc_macro, | ||
90 | what, | ||
91 | } | ||
92 | .run(verbosity)? | ||
93 | } | ||
94 | 83 | ||
95 | flags::RustAnalyzerCmd::Diagnostics(cmd) => { | 84 | flags::RustAnalyzerCmd::Diagnostics(cmd) => { |
96 | cli::diagnostics(&cmd.path, cmd.load_output_dirs, cmd.with_proc_macro)? | 85 | cli::diagnostics(&cmd.path, cmd.load_output_dirs, cmd.with_proc_macro)? |
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs index ed732eb38..76b666dc2 100644 --- a/crates/rust-analyzer/src/cli.rs +++ b/crates/rust-analyzer/src/cli.rs | |||
@@ -1,8 +1,7 @@ | |||
1 | //! Various batch processing tasks, intended primarily for debugging. | 1 | //! Various batch processing tasks, intended primarily for debugging. |
2 | 2 | ||
3 | mod load_cargo; | 3 | pub(crate) mod load_cargo; |
4 | mod analysis_stats; | 4 | mod analysis_stats; |
5 | mod analysis_bench; | ||
6 | mod diagnostics; | 5 | mod diagnostics; |
7 | mod progress_report; | 6 | mod progress_report; |
8 | mod ssr; | 7 | mod ssr; |
@@ -15,7 +14,6 @@ use syntax::{AstNode, SourceFile}; | |||
15 | use vfs::Vfs; | 14 | use vfs::Vfs; |
16 | 15 | ||
17 | pub use self::{ | 16 | pub use self::{ |
18 | analysis_bench::{BenchCmd, BenchWhat, Position}, | ||
19 | analysis_stats::AnalysisStatsCmd, | 17 | analysis_stats::AnalysisStatsCmd, |
20 | diagnostics::diagnostics, | 18 | diagnostics::diagnostics, |
21 | load_cargo::{load_workspace, load_workspace_at, LoadCargoConfig}, | 19 | load_cargo::{load_workspace, load_workspace_at, LoadCargoConfig}, |
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs deleted file mode 100644 index 49994824f..000000000 --- a/crates/rust-analyzer/src/cli/analysis_bench.rs +++ /dev/null | |||
@@ -1,196 +0,0 @@ | |||
1 | //! Benchmark operations like highlighting or goto definition. | ||
2 | |||
3 | use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; | ||
4 | |||
5 | use anyhow::{bail, format_err, Result}; | ||
6 | use hir::PrefixKind; | ||
7 | use ide::{ | ||
8 | Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol, | ||
9 | }; | ||
10 | use ide_db::{ | ||
11 | base_db::{ | ||
12 | salsa::{Database, Durability}, | ||
13 | FileId, | ||
14 | }, | ||
15 | helpers::{insert_use::InsertUseConfig, SnippetCap}, | ||
16 | }; | ||
17 | use vfs::AbsPathBuf; | ||
18 | |||
19 | use crate::cli::{ | ||
20 | load_cargo::{load_workspace_at, LoadCargoConfig}, | ||
21 | print_memory_usage, Verbosity, | ||
22 | }; | ||
23 | |||
24 | pub struct BenchCmd { | ||
25 | pub path: PathBuf, | ||
26 | pub what: BenchWhat, | ||
27 | pub memory_usage: bool, | ||
28 | pub load_output_dirs: bool, | ||
29 | pub with_proc_macro: bool, | ||
30 | } | ||
31 | |||
32 | pub enum BenchWhat { | ||
33 | Highlight { path: AbsPathBuf }, | ||
34 | Complete(Position), | ||
35 | GotoDef(Position), | ||
36 | } | ||
37 | |||
38 | #[derive(Debug, Clone)] | ||
39 | pub struct Position { | ||
40 | pub path: AbsPathBuf, | ||
41 | pub line: u32, | ||
42 | pub column: u32, | ||
43 | } | ||
44 | |||
45 | impl FromStr for Position { | ||
46 | type Err = anyhow::Error; | ||
47 | fn from_str(s: &str) -> Result<Self> { | ||
48 | let mut split = s.rsplitn(3, ':'); | ||
49 | match (split.next(), split.next(), split.next()) { | ||
50 | (Some(column), Some(line), Some(path)) => { | ||
51 | let path = env::current_dir().unwrap().join(path); | ||
52 | let path = AbsPathBuf::assert(path); | ||
53 | Ok(Position { path, line: line.parse()?, column: column.parse()? }) | ||
54 | } | ||
55 | _ => bail!("position should be in file:line:column format: {:?}", s), | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
60 | impl BenchCmd { | ||
61 | pub fn run(self, verbosity: Verbosity) -> Result<()> { | ||
62 | profile::init(); | ||
63 | |||
64 | let start = Instant::now(); | ||
65 | eprint!("loading: "); | ||
66 | |||
67 | let cargo_config = Default::default(); | ||
68 | let load_cargo_config = LoadCargoConfig { | ||
69 | load_out_dirs_from_check: self.load_output_dirs, | ||
70 | with_proc_macro: self.with_proc_macro, | ||
71 | }; | ||
72 | let (mut host, vfs, _proc_macro) = | ||
73 | load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; | ||
74 | eprintln!("{:?}\n", start.elapsed()); | ||
75 | |||
76 | let file_id = { | ||
77 | let path = match &self.what { | ||
78 | BenchWhat::Highlight { path } => path, | ||
79 | BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path, | ||
80 | }; | ||
81 | let path = path.clone().into(); | ||
82 | vfs.file_id(&path).ok_or_else(|| format_err!("Can't find {}", path))? | ||
83 | }; | ||
84 | |||
85 | match &self.what { | ||
86 | BenchWhat::Highlight { .. } => { | ||
87 | let res = do_work(&mut host, file_id, |analysis| { | ||
88 | analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | ||
89 | analysis.highlight_as_html(file_id, false).unwrap() | ||
90 | }); | ||
91 | if verbosity.is_verbose() { | ||
92 | println!("\n{}", res); | ||
93 | } | ||
94 | } | ||
95 | BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => { | ||
96 | let is_completion = matches!(self.what, BenchWhat::Complete(..)); | ||
97 | |||
98 | let offset = host | ||
99 | .analysis() | ||
100 | .file_line_index(file_id)? | ||
101 | .offset(LineCol { line: pos.line - 1, col: pos.column }); | ||
102 | let file_position = FilePosition { file_id, offset }; | ||
103 | |||
104 | if is_completion { | ||
105 | let options = CompletionConfig { | ||
106 | enable_postfix_completions: true, | ||
107 | enable_imports_on_the_fly: true, | ||
108 | add_call_parenthesis: true, | ||
109 | add_call_argument_snippets: true, | ||
110 | snippet_cap: SnippetCap::new(true), | ||
111 | insert_use: InsertUseConfig { | ||
112 | merge: None, | ||
113 | prefix_kind: PrefixKind::Plain, | ||
114 | group: true, | ||
115 | }, | ||
116 | }; | ||
117 | let res = do_work(&mut host, file_id, |analysis| { | ||
118 | analysis.completions(&options, file_position) | ||
119 | }); | ||
120 | if verbosity.is_verbose() { | ||
121 | println!("\n{:#?}", res); | ||
122 | } | ||
123 | } else { | ||
124 | let res = do_work(&mut host, file_id, |analysis| { | ||
125 | analysis.goto_definition(file_position) | ||
126 | }); | ||
127 | if verbosity.is_verbose() { | ||
128 | println!("\n{:#?}", res); | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | if self.memory_usage { | ||
135 | print_memory_usage(host, vfs); | ||
136 | } | ||
137 | |||
138 | Ok(()) | ||
139 | } | ||
140 | } | ||
141 | |||
142 | fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T { | ||
143 | { | ||
144 | let start = Instant::now(); | ||
145 | eprint!("from scratch: "); | ||
146 | work(&host.analysis()); | ||
147 | eprintln!("{:?}", start.elapsed()); | ||
148 | } | ||
149 | { | ||
150 | let start = Instant::now(); | ||
151 | eprint!("no change: "); | ||
152 | work(&host.analysis()); | ||
153 | eprintln!("{:?}", start.elapsed()); | ||
154 | } | ||
155 | { | ||
156 | let start = Instant::now(); | ||
157 | eprint!("trivial change: "); | ||
158 | host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW); | ||
159 | work(&host.analysis()); | ||
160 | eprintln!("{:?}", start.elapsed()); | ||
161 | } | ||
162 | { | ||
163 | let start = Instant::now(); | ||
164 | eprint!("comment change: "); | ||
165 | { | ||
166 | let mut text = host.analysis().file_text(file_id).unwrap().to_string(); | ||
167 | text.push_str("\n/* Hello world */\n"); | ||
168 | let mut change = Change::new(); | ||
169 | change.change_file(file_id, Some(Arc::new(text))); | ||
170 | host.apply_change(change); | ||
171 | } | ||
172 | work(&host.analysis()); | ||
173 | eprintln!("{:?}", start.elapsed()); | ||
174 | } | ||
175 | { | ||
176 | let start = Instant::now(); | ||
177 | eprint!("item change: "); | ||
178 | { | ||
179 | let mut text = host.analysis().file_text(file_id).unwrap().to_string(); | ||
180 | text.push_str("\npub fn _dummy() {}\n"); | ||
181 | let mut change = Change::new(); | ||
182 | change.change_file(file_id, Some(Arc::new(text))); | ||
183 | host.apply_change(change); | ||
184 | } | ||
185 | work(&host.analysis()); | ||
186 | eprintln!("{:?}", start.elapsed()); | ||
187 | } | ||
188 | { | ||
189 | let start = Instant::now(); | ||
190 | eprint!("const change: "); | ||
191 | host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH); | ||
192 | let res = work(&host.analysis()); | ||
193 | eprintln!("{:?}", start.elapsed()); | ||
194 | res | ||
195 | } | ||
196 | } | ||
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 8b874239c..d9a5030a0 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs | |||
@@ -39,6 +39,9 @@ mod op_queue; | |||
39 | pub mod lsp_ext; | 39 | pub mod lsp_ext; |
40 | pub mod config; | 40 | pub mod config; |
41 | 41 | ||
42 | #[cfg(test)] | ||
43 | mod benchmarks; | ||
44 | |||
42 | use serde::de::DeserializeOwned; | 45 | use serde::de::DeserializeOwned; |
43 | use std::fmt; | 46 | use std::fmt; |
44 | 47 | ||