From 8447ecf747f7488a904a47a53a770848e5beaf12 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 30 Mar 2021 11:59:00 +0300 Subject: [PATCH 1/2] 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. From fb00b92dde25af239c53192365b79682c7f6666d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 30 Mar 2021 17:20:27 +0300 Subject: [PATCH 2/2] internal: revive google_cpu_profile infra --- crates/profile/src/google_cpu_profiler.rs | 19 +++++++----- crates/profile/src/lib.rs | 37 ++++++++++++++++++++--- crates/rust-analyzer/src/benchmarks.rs | 2 ++ 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/crates/profile/src/google_cpu_profiler.rs b/crates/profile/src/google_cpu_profiler.rs index db865c65be17..cae6caeaa669 100644 --- a/crates/profile/src/google_cpu_profiler.rs +++ b/crates/profile/src/google_cpu_profiler.rs @@ -14,26 +14,31 @@ extern "C" { fn ProfilerStop(); } -static PROFILER_STATE: AtomicUsize = AtomicUsize::new(OFF); const OFF: usize = 0; const ON: usize = 1; const PENDING: usize = 2; -pub fn start(path: &Path) { - if PROFILER_STATE.compare_and_swap(OFF, PENDING, Ordering::SeqCst) != OFF { +fn transition(current: usize, new: usize) -> bool { + static STATE: AtomicUsize = AtomicUsize::new(OFF); + + STATE.compare_exchange(current, new, Ordering::SeqCst, Ordering::SeqCst).is_ok() +} + +pub(crate) fn start(path: &Path) { + if !transition(OFF, PENDING) { panic!("profiler already started"); } let path = CString::new(path.display().to_string()).unwrap(); if unsafe { ProfilerStart(path.as_ptr()) } == 0 { panic!("profiler failed to start") } - assert!(PROFILER_STATE.compare_and_swap(PENDING, ON, Ordering::SeqCst) == PENDING); + assert!(transition(PENDING, ON)); } -pub fn stop() { - if PROFILER_STATE.compare_and_swap(ON, PENDING, Ordering::SeqCst) != ON { +pub(crate) fn stop() { + if !transition(ON, PENDING) { panic!("profiler is not started") } unsafe { ProfilerStop() }; - assert!(PROFILER_STATE.compare_and_swap(PENDING, OFF, Ordering::SeqCst) == PENDING); + assert!(transition(PENDING, OFF)); } diff --git a/crates/profile/src/lib.rs b/crates/profile/src/lib.rs index 9ca6341db7f2..a31fb8f43937 100644 --- a/crates/profile/src/lib.rs +++ b/crates/profile/src/lib.rs @@ -52,7 +52,7 @@ impl Drop for Scope { /// Usage: /// 1. Install gpref_tools (https://github.com/gperftools/gperftools), probably packaged with your Linux distro. /// 2. Build with `cpu_profiler` feature. -/// 3. Tun the code, the *raw* output would be in the `./out.profile` file. +/// 3. Run the code, the *raw* output would be in the `./out.profile` file. /// 4. Install pprof for visualization (https://github.com/google/pprof). /// 5. Bump sampling frequency to once per ms: `export CPUPROFILE_FREQUENCY=1000` /// 6. Use something like `pprof -svg target/release/rust-analyzer ./out.profile` to see the results. @@ -60,8 +60,17 @@ impl Drop for Scope { /// For example, here's how I run profiling on NixOS: /// /// ```bash -/// $ nix-shell -p gperftools --run \ -/// 'cargo run --release -p rust-analyzer -- parse < ~/projects/rustbench/parser.rs > /dev/null' +/// $ bat -p shell.nix +/// with import {}; +/// mkShell { +/// buildInputs = [ gperftools ]; +/// shellHook = '' +/// export LD_LIBRARY_PATH="${gperftools}/lib:" +/// ''; +/// } +/// $ set -x CPUPROFILE_FREQUENCY 1000 +/// $ nix-shell --run 'cargo test --release --package rust-analyzer --lib -- benchmarks::benchmark_integrated_highlighting --exact --nocapture' +/// $ pprof -svg target/release/deps/rust_analyzer-8739592dc93d63cb crates/rust-analyzer/out.profile > profile.svg /// ``` /// /// See this diff for how to profile completions: @@ -81,7 +90,9 @@ pub fn cpu_span() -> CpuSpan { #[cfg(not(feature = "cpu_profiler"))] { - eprintln!("cpu_profiler feature is disabled") + eprintln!( + r#"cpu profiling is disabled, uncomment `default = [ "cpu_profiler" ]` in Cargo.toml to enable."# + ) } CpuSpan { _private: () } @@ -91,7 +102,23 @@ impl Drop for CpuSpan { fn drop(&mut self) { #[cfg(feature = "cpu_profiler")] { - google_cpu_profiler::stop() + google_cpu_profiler::stop(); + let profile_data = std::env::current_dir().unwrap().join("out.profile"); + eprintln!("Profile data saved to:\n\n {}\n", profile_data.display()); + let mut cmd = std::process::Command::new("pprof"); + cmd.arg("-svg").arg(std::env::current_exe().unwrap()).arg(&profile_data); + let out = cmd.output(); + + match out { + Ok(out) if out.status.success() => { + let svg = profile_data.with_extension("svg"); + std::fs::write(&svg, &out.stdout).unwrap(); + eprintln!("Profile rendered to:\n\n {}\n", svg.display()); + } + _ => { + eprintln!("Failed to run:\n\n {:?}\n", cmd); + } + } } } } diff --git a/crates/rust-analyzer/src/benchmarks.rs b/crates/rust-analyzer/src/benchmarks.rs index a6f997af80d4..bf569b40bb60 100644 --- a/crates/rust-analyzer/src/benchmarks.rs +++ b/crates/rust-analyzer/src/benchmarks.rs @@ -51,6 +51,7 @@ fn benchmark_integrated_highlighting() { } profile::init_from("*>100"); + // let _s = profile::heartbeat_span(); { let _it = stdx::timeit("change"); @@ -63,6 +64,7 @@ fn benchmark_integrated_highlighting() { { let _it = stdx::timeit("after change"); + let _span = profile::cpu_span(); let analysis = host.analysis(); analysis.highlight_as_html(file_id, false).unwrap(); }