From 8dde4df4d12ad06d7ee4ee9f6e12659dadc909ee Mon Sep 17 00:00:00 2001 From: Ilya Zlobintsev Date: Mon, 10 Feb 2025 19:25:15 +0200 Subject: [PATCH] perf: disable plot supersampling, add plot render benchmarks (#467) * dev: add benchmark for drawing plots * chore: update bench * perf: disable supersampling --- Cargo.lock | 3 +- Cargo.toml | 3 + lact-daemon/Cargo.toml | 6 - lact-daemon/benches/daemon.rs | 5 - lact-gui/Cargo.toml | 5 +- lact-gui/src/app/graphs_window/mod.rs | 5 +- lact-gui/src/app/graphs_window/plot/imp.rs | 70 ++++++++++- .../app/graphs_window/plot/render_thread.rs | 117 ++++++++++-------- lact/Cargo.toml | 9 ++ lact/benches/bench.rs | 7 ++ 10 files changed, 157 insertions(+), 73 deletions(-) delete mode 100644 lact-daemon/benches/daemon.rs create mode 100644 lact/benches/bench.rs diff --git a/Cargo.lock b/Cargo.lock index acbf562..c9d0833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1282,6 +1282,7 @@ name = "lact" version = "0.7.1" dependencies = [ "anyhow", + "divan", "lact-cli", "lact-daemon", "lact-gui", @@ -1327,7 +1328,6 @@ dependencies = [ "futures", "indexmap", "insta", - "lact-daemon", "lact-schema", "libdrm_amdgpu_sys", "libflate", @@ -1358,6 +1358,7 @@ dependencies = [ "anyhow", "cairo-rs", "chrono", + "divan", "gtk4", "itertools", "lact-client", diff --git a/Cargo.toml b/Cargo.toml index 02f2240..1ffb1cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ chrono = "0.4.31" indexmap = { version = "2.5.0", features = ["serde"] } pretty_assertions = "1.4.0" divan = "0.1" +gtk = { version = "0.9", package = "gtk4" } [profile.release] strip = "symbols" @@ -45,3 +46,5 @@ opt-level = 3 [profile.bench] strip = false debug = 1 +lto = "thin" +codegen-units = 256 diff --git a/lact-daemon/Cargo.toml b/lact-daemon/Cargo.toml index 84fb3c2..fd7dad7 100644 --- a/lact-daemon/Cargo.toml +++ b/lact-daemon/Cargo.toml @@ -48,14 +48,8 @@ copes = { git = "https://gitlab.com/corectrl/copes" } libloading = "0.8.6" [dev-dependencies] -divan = { workspace = true } pretty_assertions = { workspace = true } -lact-daemon = { path = ".", features = ["bench"] } insta = { version = "1.41.1", features = ["json", "yaml"] } [build-dependencies] bindgen = "0.68" - -[[bench]] -name = "daemon" -harness = false diff --git a/lact-daemon/benches/daemon.rs b/lact-daemon/benches/daemon.rs deleted file mode 100644 index f90e26e..0000000 --- a/lact-daemon/benches/daemon.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - // Include the daemon lib - let _ = lact_daemon::MODULE_CONF_PATH; - divan::main(); -} diff --git a/lact-gui/Cargo.toml b/lact-gui/Cargo.toml index b746f70..35111b7 100644 --- a/lact-gui/Cargo.toml +++ b/lact-gui/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" default = ["gtk-tests"] gtk-tests = [] adw = ["dep:adw", "relm4/libadwaita"] +bench = ["dep:divan"] [dependencies] lact-client = { path = "../lact-client" } @@ -19,8 +20,8 @@ tracing = { workspace = true } anyhow = { workspace = true } tracing-subscriber = { workspace = true } chrono = { workspace = true } +gtk = { workspace = true, features = ["v4_6", "blueprint"] } -gtk = { version = "0.9", package = "gtk4", features = ["v4_6", "blueprint"] } adw = { package = "libadwaita", version = "0.7.1", features = [ "v1_4", ], optional = true } @@ -37,5 +38,7 @@ itertools = "0.13.0" thread-priority = "1.1.0" +divan = { workspace = true, optional = true } + [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/lact-gui/src/app/graphs_window/mod.rs b/lact-gui/src/app/graphs_window/mod.rs index 0336bd4..493b59f 100644 --- a/lact-gui/src/app/graphs_window/mod.rs +++ b/lact-gui/src/app/graphs_window/mod.rs @@ -38,12 +38,13 @@ impl relm4::Component for GraphsWindow { set_margin_all: 10, set_row_spacing: 20, set_column_spacing: 20, + set_column_homogeneous: true, attach[0, 0, 1, 1]: temperature_plot = &Plot { set_title: "Temperature", set_hexpand: true, set_value_suffix: "°C", - set_y_label_area_relative_size: 0.15, + set_y_label_area_relative_size: 0.2, #[watch] set_time_period_seconds: model.time_period_seconds_adj.value() as i64, }, @@ -52,7 +53,7 @@ impl relm4::Component for GraphsWindow { set_title: "Fan speed", set_hexpand: true, set_value_suffix: "RPM", - set_y_label_area_relative_size: 0.25, + set_y_label_area_relative_size: 0.3, set_secondary_y_label_area_relative_size: 0.15, #[watch] set_time_period_seconds: model.time_period_seconds_adj.value() as i64, diff --git a/lact-gui/src/app/graphs_window/plot/imp.rs b/lact-gui/src/app/graphs_window/plot/imp.rs index fe5d7ce..aece33e 100644 --- a/lact-gui/src/app/graphs_window/plot/imp.rs +++ b/lact-gui/src/app/graphs_window/plot/imp.rs @@ -9,6 +9,8 @@ use std::collections::BTreeMap; use super::render_thread::{RenderRequest, RenderThread}; +const SUPERSAMPLE_FACTOR: u32 = 1; + #[derive(Properties, Default)] #[properties(wrapper_type = super::Plot)] pub struct Plot { @@ -75,7 +77,7 @@ impl WidgetImpl for Plot { secondary_y_label_relative_area_size: self .secondary_y_label_area_relative_size .get(), - supersample_factor: 4, + supersample_factor: SUPERSAMPLE_FACTOR, time_period_seconds: self.time_period_seconds.get(), }); } @@ -105,14 +107,19 @@ impl PlotData { self.push_secondary_line_series_with_time(name, point, chrono::Local::now().naive_local()); } - fn push_line_series_with_time(&mut self, name: &str, point: f64, time: NaiveDateTime) { + pub(super) fn push_line_series_with_time( + &mut self, + name: &str, + point: f64, + time: NaiveDateTime, + ) { self.line_series .entry(name.to_owned()) .or_default() .push((time.and_utc().timestamp_millis(), point)); } - pub fn push_secondary_line_series_with_time( + pub(super) fn push_secondary_line_series_with_time( &mut self, name: &str, point: f64, @@ -188,3 +195,60 @@ impl PlotData { self.line_series.is_empty() && self.secondary_line_series.is_empty() } } + +#[cfg(feature = "bench")] +mod benches { + use crate::app::graphs_window::plot::{ + render_thread::{process_request, RenderRequest}, + PlotData, + }; + use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; + use divan::{counter::ItemsCount, Bencher}; + use std::sync::Mutex; + + use super::SUPERSAMPLE_FACTOR; + + #[divan::bench] + fn render_plot(bencher: Bencher) { + let last_texture = &Mutex::new(None); + + bencher + .with_inputs(sample_plot_data) + .input_counter(|_| ItemsCount::new(1usize)) + .bench_values(|data| { + let request = RenderRequest { + title: "bench render".into(), + value_suffix: "%".into(), + secondary_value_suffix: "".into(), + y_label_area_relative_size: 1.0, + secondary_y_label_relative_area_size: 1.0, + data, + width: 1920, + height: 1080, + supersample_factor: SUPERSAMPLE_FACTOR, + time_period_seconds: 60, + }; + + process_request(request, last_texture) + }); + } + + fn sample_plot_data() -> PlotData { + let mut data = PlotData::default(); + + // Simulate 1 minute plot with 4 values per second + for sec in 0..60 { + for milli in [0, 250, 500, 750] { + let datetime = NaiveDateTime::new( + NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), + NaiveTime::from_hms_milli_opt(0, 0, sec, milli).unwrap(), + ); + + data.push_line_series_with_time("GPU", 100.0, datetime); + data.push_secondary_line_series_with_time("GPU Secondary", 10.0, datetime); + } + } + + data + } +} diff --git a/lact-gui/src/app/graphs_window/plot/render_thread.rs b/lact-gui/src/app/graphs_window/plot/render_thread.rs index fc2dce6..52cdfdd 100644 --- a/lact-gui/src/app/graphs_window/plot/render_thread.rs +++ b/lact-gui/src/app/graphs_window/plot/render_thread.rs @@ -94,62 +94,15 @@ impl RenderThread { match current_request.take() { Some(Request::Render(render_request)) => { - // Create a new ImageSurface for Cairo rendering. - let mut surface = ImageSurface::create( - cairo::Format::ARgb32, - (render_request.width * render_request.supersample_factor) as i32, - (render_request.height * render_request.supersample_factor) as i32, - ) - .unwrap(); - - let cairo_context = CairoContext::new(&surface).unwrap(); - - // Don't use Cairo's default antialiasing, it makes the lines look too blurry - // Supersampling is our 2D anti-aliasing solution. - if render_request.supersample_factor > 1 { - cairo_context.set_antialias(cairo::Antialias::None); - } - - let cairo_backend = CairoBackend::new( - &cairo_context, - // Supersample the rendering - ( - render_request.width * render_request.supersample_factor, - render_request.height * render_request.supersample_factor, - ), - ) - .unwrap(); - - if let Err(err) = render_request.draw(cairo_backend) { - error!("Failed to plot chart: {err:?}") - } - - match ( - surface.to_texture(), - last_texture.lock().unwrap().deref_mut(), - ) { - // Successfully generated a new texture, but the old texture is also there - (Some(texture), Some(last_texture)) => { - *last_texture = texture; - } - // If texture conversion failed, keep the old texture if it's present. - (None, None) => { - error!("Failed to convert cairo surface to gdk texture, not overwriting old one"); - } - // Update the last texture, if The old texture wasn't ever generated (None), - // No matter the result of conversion - (result, last_texture) => { - *last_texture = result; - } - }; - } + process_request(render_request, last_texture); + } // Terminate the thread if a Terminate request is received. Some(Request::Terminate) => break, None => {} } } - }) - .unwrap(); + }) + .unwrap(); Self { state, @@ -177,6 +130,60 @@ impl RenderThread { } } +pub(super) fn process_request( + render_request: RenderRequest, + last_texture: &Mutex>, +) { + // Create a new ImageSurface for Cairo rendering. + let mut surface = ImageSurface::create( + cairo::Format::ARgb32, + (render_request.width * render_request.supersample_factor) as i32, + (render_request.height * render_request.supersample_factor) as i32, + ) + .unwrap(); + + let cairo_context = CairoContext::new(&surface).unwrap(); + + // Don't use Cairo's default antialiasing, it makes the lines look too blurry + // Supersampling is our 2D anti-aliasing solution. + if render_request.supersample_factor > 1 { + cairo_context.set_antialias(cairo::Antialias::None); + } + + let cairo_backend = CairoBackend::new( + &cairo_context, + // Supersample the rendering + ( + render_request.width * render_request.supersample_factor, + render_request.height * render_request.supersample_factor, + ), + ) + .unwrap(); + + if let Err(err) = render_request.draw(cairo_backend) { + error!("Failed to plot chart: {err:?}") + } + + match ( + surface.to_texture(), + last_texture.lock().unwrap().deref_mut(), + ) { + // Successfully generated a new texture, but the old texture is also there + (Some(texture), Some(last_texture)) => { + *last_texture = texture; + } + // If texture conversion failed, keep the old texture if it's present. + (None, None) => { + error!("Failed to convert cairo surface to gdk texture, not overwriting old one"); + } + // Update the last texture, if The old texture wasn't ever generated (None), + // No matter the result of conversion + (result, last_texture) => { + *last_texture = result; + } + }; +} + // Implement the default constructor for RenderThread using the `new` method. impl Default for RenderThread { fn default() -> Self { @@ -336,12 +343,12 @@ impl RenderRequest { (current_date, segment.evaluate(current_date)) }) }), - Palette99::pick(idx).stroke_width(8), + Palette99::pick(idx).stroke_width(2), )) .context("Failed to draw series")? .label(caption) .legend(move |(x, y)| { - let offset = self.relative_size(0.04) as i32; + let offset = self.relative_size(0.02) as i32; Rectangle::new( [(x - offset, y - offset), (x + offset, y + offset)], Palette99::pick(idx).filled(), @@ -360,12 +367,12 @@ impl RenderRequest { (current_date, segment.evaluate(current_date)) }) }), - Palette99::pick(idx + 10).stroke_width(8), + Palette99::pick(idx + 10).stroke_width(2), )) .context("Failed to draw series")? .label(caption) .legend(move |(x, y)| { - let offset = self.relative_size(0.04) as i32; + let offset = self.relative_size(0.02) as i32; Rectangle::new( [(x - offset, y - offset), (x + offset, y + offset)], Palette99::pick(idx + 10).filled(), diff --git a/lact/Cargo.toml b/lact/Cargo.toml index 7d114cc..5404dd6 100644 --- a/lact/Cargo.toml +++ b/lact/Cargo.toml @@ -13,3 +13,12 @@ lact-schema = { path = "../lact-schema", features = ["args"] } lact-cli = { path = "../lact-cli" } lact-gui = { path = "../lact-gui", optional = true } anyhow = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +lact-daemon = { path = "../lact-daemon", features = ["bench"] } +lact-gui = { path = "../lact-gui", features = ["bench"] } + +[[bench]] +name = "bench" +harness = false diff --git a/lact/benches/bench.rs b/lact/benches/bench.rs new file mode 100644 index 0000000..ac09dbe --- /dev/null +++ b/lact/benches/bench.rs @@ -0,0 +1,7 @@ +fn main() { + // Include crates in the binary + let _ = lact_daemon::run; + let _ = lact_gui::run; + + divan::main(); +}