From ab8652ee3e2bc5b7872358381c84bd5546c7930c Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 24 May 2022 14:04:02 -0400 Subject: [PATCH] HIGHLY optimize partial regen of values --- Cargo.lock | 12 ---- Cargo.toml | 1 - src/function_entry.rs | 149 +++++++++++++++++++++++++----------------- src/lib.rs | 1 - src/math_app.rs | 9 ++- src/misc.rs | 110 +------------------------------ tests/misc.rs | 28 -------- 7 files changed, 99 insertions(+), 211 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f006fec..d32b91e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2373,17 +2373,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" -[[package]] -name = "unzip-n" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7e85a0596447f0f2ac090e16bc4c516c6fe91771fb0c0ccf7fa3dae896b9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "url" version = "2.2.2" @@ -2816,7 +2805,6 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-wasm", - "unzip-n", "wasm-bindgen", "web-sys", "wee_alloc", diff --git a/Cargo.toml b/Cargo.toml index ad6e52e..1861add 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,6 @@ itertools = "0.10" static_assertions = "1.1" bincode = "1.3" serde = "1" -unzip-n = "0.1" [dev-dependencies] benchmarks = { path = "./benchmarks" } diff --git a/src/function_entry.rs b/src/function_entry.rs index d96ee35..0fd7284 100644 --- a/src/function_entry.rs +++ b/src/function_entry.rs @@ -13,7 +13,6 @@ use std::{ fmt::{self, Debug}, intrinsics::assume, }; -use unzip_n::unzip_n; /// Represents the possible variations of Riemann Sums #[derive(PartialEq, Eq, Debug, Copy, Clone)] @@ -257,7 +256,8 @@ impl FunctionEntry { /// Does the calculations and stores results in `self` pub fn calculate( - &mut self, width_changed: bool, min_max_changed: bool, settings: &AppSettings, + &mut self, width_changed: bool, min_max_changed: bool, did_zoom: bool, + settings: &AppSettings, ) { if self.test_result.is_some() { return; @@ -279,68 +279,99 @@ impl FunctionEntry { if width_changed { self.invalidate_back(); self.invalidate_derivative(); - } else if min_max_changed && !self.back_data.is_empty() { + } else if min_max_changed && !self.back_data.is_empty() && !did_zoom { partial_regen = true; - let x_data_1: Vec = self.back_data.iter().map(|ele| ele.x).collect::>(); - let x_data: SteppedVector = x_data_1.as_slice().into(); + let prev_min = unsafe { self.back_data.first().unwrap_unchecked() }.x; - let do_nth_derivative = self.nth_derviative && self.nth_derivative_data.is_some(); + if prev_min < settings.min_x { + let min_i = ((settings.min_x - prev_min) as f64 / resolution) as usize; - let nth_derivative_data = self.nth_derivative_data.as_ref(); - unzip_n!(3); - let (back_data, derivative_data_1, new_nth_derivative_data): ( - Vec, - Vec, - Vec>, - ) = resolution_iter - .clone() - .into_iter() - .map(|x| { - if let Some(i) = x_data.get_index(x) { - ( - self.back_data[i], - self.derivative_data[i], - do_nth_derivative.then(|| unsafe { - nth_derivative_data.map(|data| data[i]).unwrap_unchecked() - }), - ) - } else { - ( - Value::new(x, self.function.get(x)), - Value::new(x, self.function.get_derivative_1(x)), - do_nth_derivative.then(|| { - Value::new(x, self.function.get_nth_derivative(self.curr_nth, x)) - }), - ) - } - }) - .collect::)>>() - .into_iter() - .unzip_n_vec(); + { + let (cut_data, _) = self.back_data.split_at(min_i); - debug_assert_eq!(back_data.len(), settings.plot_width + 1); - debug_assert_eq!(derivative_data_1.len(), settings.plot_width + 1); + let new_data: Vec = (min_i..=settings.plot_width) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get(x))) + .collect(); + self.back_data = [cut_data, &new_data].concat(); + debug_assert_eq!(self.back_data.len(), settings.plot_width + 1); + } - self.back_data = back_data; + { + let (cut_data, _) = self.derivative_data.split_at(min_i); - self.derivative_data = derivative_data_1; + let new_data: Vec = (min_i..=settings.plot_width) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get_derivative_1(x))) + .collect(); + self.derivative_data = [cut_data, &new_data].concat(); + debug_assert_eq!(self.derivative_data.len(), settings.plot_width + 1); + } - if do_nth_derivative { - /* - debug_assert!(new_nth_derivative_data.iter().any(|x| x.is_none())); - self.nth_derivative_data = Some(unsafe { - std::mem::transmute::>, Vec>(new_nth_derivative_data) - }); - */ - self.nth_derivative_data = Some( - new_nth_derivative_data - .into_iter() - .map(|ele| unsafe { ele.unwrap_unchecked() }) - .collect(), - ); + if self.nth_derviative && let Some(data) = self.nth_derivative_data.as_mut() { + let (cut_data, _) = data.split_at(min_i); + + let new_data: Vec = (min_i..=settings.plot_width) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get_nth_derivative(self.curr_nth, x))) + .collect(); + *data = [cut_data, &new_data].concat(); + debug_assert_eq!(data.len(), settings.plot_width + 1); + } } else { - self.invalidate_nth(); + let min_i = ((settings.max_x - prev_min) as f64 / resolution) as usize; + let min_i_2 = settings.plot_width - min_i; + + { + let (_, cut_data) = self.back_data.split_at(min_i); + + let new_data_1: Vec = (0..min_i) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get(x))) + .collect(); + + let new_data_2: Vec = (min_i..min_i_2) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get(x))) + .collect(); + + self.back_data = [&new_data_1, cut_data, &new_data_2].concat(); + debug_assert_eq!(self.back_data.len(), settings.plot_width + 1); + } + + { + let (_, cut_data) = self.derivative_data.split_at(min_i); + + let new_data_1: Vec = (0..min_i) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get_derivative_1(x))) + .collect(); + + let new_data_2: Vec = (min_i..min_i_2) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get_derivative_1(x))) + .collect(); + + self.derivative_data = [&new_data_1, cut_data, &new_data_2].concat(); + debug_assert_eq!(self.derivative_data.len(), settings.plot_width + 1); + } + + if self.nth_derviative && let Some(data) = self.nth_derivative_data.as_mut() { + let (_, cut_data) = data.split_at(min_i); + + let new_data_1: Vec = (0..min_i) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get_nth_derivative(self.curr_nth, x))) + .collect(); + + let new_data_2: Vec = (min_i..min_i_2) + .map(move |x: usize| (x as f64 * resolution) + settings.min_x) + .map(|x: f64| Value::new(x, self.function.get_nth_derivative(self.curr_nth, x))) + .collect(); + *data = [&new_data_1, cut_data, &new_data_2].concat(); + debug_assert_eq!(data.len(), settings.plot_width + 1); + } } } else { self.invalidate_back(); @@ -547,7 +578,7 @@ impl FunctionEntry { ) { let mut settings = settings; { - self.calculate(true, true, &settings); + self.calculate(true, true, false, &settings); assert!(!self.back_data.is_empty()); assert_eq!(self.back_data.len(), settings.plot_width + 1); @@ -594,7 +625,7 @@ impl FunctionEntry { { settings.min_x += 1.0; settings.max_x += 1.0; - self.calculate(true, true, &settings); + self.calculate(true, true, false, &settings); let a = self .derivative_data @@ -656,7 +687,7 @@ impl FunctionEntry { { settings.min_x -= 2.0; settings.max_x -= 2.0; - self.calculate(true, true, &settings); + self.calculate(true, true, false, &settings); let a = self .derivative_data @@ -735,7 +766,7 @@ impl FunctionEntry { settings.min_x -= 1.0; settings.max_x -= 1.0; - self.calculate(true, true, &settings); + self.calculate(true, true, false, &settings); assert!(!self.back_data.is_empty()); assert!(self.integral_data.is_none()); diff --git a/src/lib.rs b/src/lib.rs index c8d53c7..f2a0e01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,6 @@ pub use crate::{ option_vec_printer, step_helper, EguiHelper, - SteppedVector, }, }; diff --git a/src/math_app.rs b/src/math_app.rs index 03ccca8..ae13c97 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -594,6 +594,8 @@ impl App for MathApp { let max_x: f64 = bounds.max()[0]; let min_max_changed = (min_x != self.settings.min_x) | (max_x != self.settings.max_x); + let did_zoom = (max_x - min_x).abs() + != (self.settings.max_x - self.settings.min_x).abs(); self.settings.min_x = min_x; self.settings.max_x = max_x; @@ -601,7 +603,12 @@ impl App for MathApp { .get_entries_mut() .iter_mut() .for_each(|(_, function)| { - function.calculate(width_changed, min_max_changed, &self.settings) + function.calculate( + width_changed, + min_max_changed, + did_zoom, + &self.settings, + ) }); let area: Vec> = self diff --git a/src/misc.rs b/src/misc.rs index 87a7165..b23e837 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,117 +1,9 @@ -use std::{intrinsics::assume, ops::RangeInclusive}; +use std::intrinsics::assume; use egui::plot::{Line, Points, Value, Values}; use getrandom::getrandom; use itertools::Itertools; -/// [`SteppedVector`] is used in order to efficiently sort through an ordered -/// `Vec` Used in order to speedup the processing of cached data when -/// moving horizontally without zoom in `FunctionEntry`. Before this struct, the -/// index was calculated with `.iter().position(....` which was horribly -/// inefficient -pub struct SteppedVector<'a> { - /// Actual data being referenced. HAS to be sorted from minimum to maximum - data: &'a [f64], - - /// Since all entries in `data` are evenly spaced, this field stores the step between 2 adjacent elements - step: f64, - - range: RangeInclusive, -} - -impl<'a> SteppedVector<'a> { - /// Returns `Option` with index of element with value `x`. and `None` if `x` does not exist in `data` - #[inline] - pub fn get_index(&self, x: f64) -> Option { - debug_assert!(!x.is_nan()); - debug_assert!(self.step > 0.0); - debug_assert!(self.step.is_sign_positive()); - debug_assert!(self.step.is_finite()); - debug_assert!(self.data.len() >= 2); - - unsafe { - assume(!self.step.is_nan()); - assume(self.step > 0.0); - assume(self.step.is_sign_positive()); - assume(self.step.is_finite()); - assume(self.data.len() >= 2); - } - - if !self.range.contains(&x) { - return None; - } - - if &x == self.get_min() { - return Some(0); - } else if &x == self.get_max() { - return Some(self.data.len() - 1); - } - - // Do some math in order to calculate the expected index value - let possible_i = (x - self.get_min() / self.step) as usize; - - // Make sure that the index is valid by checking the data returned vs the actual data (just in case) - if self.data.get(possible_i) == Some(&x) { - // It is valid! - Some(possible_i) - } else { - // (For some reason) it wasn't! - None - } - } - - #[inline] - #[allow(dead_code)] - pub const fn get_min(&self) -> &f64 { self.range.start() } - - #[inline] - #[allow(dead_code)] - pub const fn get_max(&self) -> &f64 { self.range.end() } - - #[allow(dead_code)] - pub fn get_data(&self) -> &'a [f64] { self.data } -} - -// Convert `&[f64]` into [`SteppedVector`] -impl<'a> From<&'a [f64]> for SteppedVector<'a> { - fn from(data: &'a [f64]) -> SteppedVector { - // Ensure data is of correct length - debug_assert!(data.len() > 2); - - // check on debug if data is sorted - debug_assert!(data.windows(2).all(|w| w[0] <= w[1])); - - unsafe { - assume(data.len() > 2); - assume(!data.is_empty()); - } - - // length of data subtracted by 1 (represents the maximum index value) - let max: f64 = data[data.len() - 1]; // The max value should be the last element - let min: f64 = data[0]; // The minimum value should be the first element - - debug_assert!(max > min); - - unsafe { - assume(max > min); - } - - // Calculate the step between elements - let step = (max - min) / (data.len() as f64); - - debug_assert!(step.is_sign_positive()); - debug_assert!(step.is_finite()); - debug_assert!(step > 0.0); - - // Create and return the struct - SteppedVector { - data, - step, - range: min..=max, - } - } -} - /// Implements traits that are useful when dealing with Vectors of egui's `Value` pub trait EguiHelper { /// Converts to `egui::plot::Values` diff --git a/tests/misc.rs b/tests/misc.rs index f140693..b9401cb 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -1,31 +1,3 @@ -/// Tests [`SteppedVector`] to ensure everything works properly (helped me find a bunch of issues) -#[test] -fn stepped_vector() { - use ytbn_graphing_software::SteppedVector; - - let min: i32 = -1000; - let max: i32 = 1000; - let data: Vec = (min..=max).map(|x| x as f64).collect(); - let len_data = data.len(); - let stepped_vector: SteppedVector = SteppedVector::from(data.as_slice()); - - assert_eq!(*stepped_vector.get_min(), min as f64); - assert_eq!(*stepped_vector.get_max(), max as f64); - - assert_eq!(stepped_vector.get_index(min as f64), Some(0)); - assert_eq!(stepped_vector.get_index(max as f64), Some(len_data - 1)); - - for i in min..=max { - assert_eq!( - stepped_vector.get_index(i as f64), - Some((i + min.abs()) as usize) - ); - } - - assert_eq!(stepped_vector.get_index((min - 1) as f64), None); - assert_eq!(stepped_vector.get_index((max + 1) as f64), None); -} - /* /// Ensures [`decimal_round`] returns correct values #[test]