From 0b3fe689cd6e8e1cd37f2d4905a25becc6acaa94 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Fri, 9 Aug 2024 15:12:44 -0700 Subject: [PATCH] Rust trial: write benchmark --- lang-trials/rust-benchmark/.gitignore | 3 + lang-trials/rust-benchmark/Cargo.toml | 35 +++++ lang-trials/rust-benchmark/index.html | 9 ++ lang-trials/rust-benchmark/main.css | 23 +++ lang-trials/rust-benchmark/notes | 13 ++ lang-trials/rust-benchmark/src/engine.rs | 176 +++++++++++++++++++++++ lang-trials/rust-benchmark/src/main.rs | 86 +++++++++++ 7 files changed, 345 insertions(+) create mode 100644 lang-trials/rust-benchmark/.gitignore create mode 100644 lang-trials/rust-benchmark/Cargo.toml create mode 100644 lang-trials/rust-benchmark/index.html create mode 100644 lang-trials/rust-benchmark/main.css create mode 100644 lang-trials/rust-benchmark/notes create mode 100644 lang-trials/rust-benchmark/src/engine.rs create mode 100644 lang-trials/rust-benchmark/src/main.rs diff --git a/lang-trials/rust-benchmark/.gitignore b/lang-trials/rust-benchmark/.gitignore new file mode 100644 index 0000000..5b910ca --- /dev/null +++ b/lang-trials/rust-benchmark/.gitignore @@ -0,0 +1,3 @@ +target/* +dist/* +Cargo.lock \ No newline at end of file diff --git a/lang-trials/rust-benchmark/Cargo.toml b/lang-trials/rust-benchmark/Cargo.toml new file mode 100644 index 0000000..8dde7b0 --- /dev/null +++ b/lang-trials/rust-benchmark/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "sycamore-trial" +version = "0.1.0" +authors = ["Aaron"] +edition = "2021" + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +nalgebra = "0.33.0" +sycamore = "0.9.0-beta.2" +typenum = "1.17.0" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } + +[dependencies.web-sys] +version = "0.3.69" +features = [ + 'CanvasRenderingContext2d', + 'HtmlCanvasElement', + 'Window', + 'Performance' +] + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" + +[profile.release] +opt-level = "s" # optimize for small code size +debug = true # include debug symbols diff --git a/lang-trials/rust-benchmark/index.html b/lang-trials/rust-benchmark/index.html new file mode 100644 index 0000000..82823fc --- /dev/null +++ b/lang-trials/rust-benchmark/index.html @@ -0,0 +1,9 @@ + + + + + The circular law + + + + diff --git a/lang-trials/rust-benchmark/main.css b/lang-trials/rust-benchmark/main.css new file mode 100644 index 0000000..f79e62c --- /dev/null +++ b/lang-trials/rust-benchmark/main.css @@ -0,0 +1,23 @@ +body { + margin-left: 20px; + margin-top: 20px; + color: #fcfcfc; + background-color: #202020; +} + +#app { + display: flex; + flex-direction: column; + width: 600px; +} + +canvas { + float: left; + background-color: #020202; + border-radius: 10px; + margin-top: 5px; +} + +input { + margin-top: 5px; +} \ No newline at end of file diff --git a/lang-trials/rust-benchmark/notes b/lang-trials/rust-benchmark/notes new file mode 100644 index 0000000..79a4030 --- /dev/null +++ b/lang-trials/rust-benchmark/notes @@ -0,0 +1,13 @@ +in profiling, most time is being spent in the `reflect` method: + +f64: + sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::h7899977a4ba0b1d3 + sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::hc337c3cb6e3b4061 + sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect_rows::h43d0f6838d0c2833 + +f32: + sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::h0e8ec322f198f847 + sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::h9928bdd5e72743ea + sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect_rows::h49f571fd8fc9b0f2 + +in one test, we spent 4000 ms in "WASM closure", but the enveloping "VoidFunction" takes 1300 ms longer. in another test, though, there's no overhang; the 7000 ms we spent in `rand_eigval_series` accounts for basically the entire load time, and matches the clock timing diff --git a/lang-trials/rust-benchmark/src/engine.rs b/lang-trials/rust-benchmark/src/engine.rs new file mode 100644 index 0000000..3b2b290 --- /dev/null +++ b/lang-trials/rust-benchmark/src/engine.rs @@ -0,0 +1,176 @@ +use nalgebra::{*, allocator::Allocator}; +use std::f64::consts::{PI, E}; +use web_sys::console; +/*use std::ops::Sub;*/ +/*use typenum::{B1, UInt, UTerm};*/ + +/*pub fn eigvals_rotated(A: SMatrix, time: f64): complex_eigenvalues(&self) -> OVector, D>*/ + +/* static matrices. should only be used when the dimension is really small */ +/*pub fn rand_eigval_series(time_res: usize) -> Vec, N>> + where + N: ToTypenum + DimName + DimSub, + DefaultAllocator: + Allocator + + Allocator + + Allocator<>::Output> + + Allocator>::Output> +{ + // initialize the random matrix + let dim = N::try_to_usize().unwrap(); + console::log_1(&format!("dimension {dim}").into()); + let mut rand_mat = OMatrix::::from_fn(|j, k| { + let n = j*dim + k; + E*((n*n) as f64) % 2.0 - 1.0 + }) * (3.0 / (dim as f64)).sqrt(); + /*let mut rand_mat = OMatrix::::identity();*/ + + // initialize the rotation step + let mut rot_step = OMatrix::::identity(); + let max_freq = 4; + for n in (0..dim).step_by(2) { + let ang = PI * ((n % max_freq) as f64) / (time_res as f64); + let ang_cos = ang.cos(); + let ang_sin = ang.sin(); + rot_step[(n, n)] = ang_cos; + rot_step[(n+1, n)] = ang_sin; + rot_step[(n, n+1)] = -ang_sin; + rot_step[(n+1, n+1)] = ang_cos; + } + + // find the eigenvalues + let mut eigval_series = Vec::, N>>::with_capacity(time_res); + console::log_1(&"before engine eigenvalues".into()); + eigval_series.push(rand_mat.complex_eigenvalues()); + console::log_1(&"after engine eigenvalues".into()); + for _ in 1..time_res { + rand_mat = &rot_step * rand_mat; + eigval_series.push(rand_mat.complex_eigenvalues()); + } + eigval_series +}*/ + +/* another attempt at static matrices. i couldn't get the types to work out */ +/*pub fn random_eigval_series(time_res: usize) -> Vec, Const>> + where + Const: ToTypenum, + as ToTypenum>::Typenum: Sub>, + < as ToTypenum>::Typenum as Sub>>::Output: ToConst +{ + // initialize the random matrix + /*let mut rand_mat = SMatrix::::zeros(); + for n in 0..N*N { + rand_mat[n] = E*((n*n) as f64) % 2.0 - 1.0; + }*/ + let rand_mat = OMatrix::, Const>::from_fn(|j, k| { + let n = j*N + k; + E*((n*n) as f64) % 2.0 - 1.0 + }); + + // initialize the rotation step + let mut rot_step = OMatrix::, Const>::identity(); + let max_freq = 4; + for n in (0..N).step_by(2) { + let ang = PI * ((n % max_freq) as f64) / (time_res as f64); + let ang_cos = ang.cos(); + let ang_sin = ang.sin(); + rot_step[(n, n)] = ang_cos; + rot_step[(n+1, n)] = ang_sin; + rot_step[(n, n+1)] = -ang_sin; + rot_step[(n+1, n+1)] = ang_cos; + } + + // find the eigenvalues + let mut eigvals = Vec::, Const>>::with_capacity(time_res); + unsafe { eigvals.set_len(time_res); } + for t in 0..time_res { + eigvals[t] = rand_mat.complex_eigenvalues(); + } + eigvals +}*/ + +/* dynamic matrices */ +pub fn rand_eigval_series(time_res: usize) -> Vec, Dyn>> + where + N: ToTypenum + DimName + DimSub, + DefaultAllocator: + Allocator + + Allocator + + Allocator<>::Output> + + Allocator>::Output> +{ + // initialize the random matrix + let dim = N::try_to_usize().unwrap(); + console::log_1(&format!("dimension {dim}").into()); + let mut rand_mat = DMatrix::::from_fn(dim, dim, |j, k| { + let n = j*dim + k; + E*((n*n) as f64) % 2.0 - 1.0 + }) * (3.0 / (dim as f64)).sqrt(); + + // initialize the rotation step + let mut rot_step = DMatrix::::identity(dim, dim); + let max_freq = 4; + for n in (0..dim).step_by(2) { + let ang = PI * ((n % max_freq) as f64) / (time_res as f64); + let ang_cos = ang.cos(); + let ang_sin = ang.sin(); + rot_step[(n, n)] = ang_cos; + rot_step[(n+1, n)] = ang_sin; + rot_step[(n, n+1)] = -ang_sin; + rot_step[(n+1, n+1)] = ang_cos; + } + + // find the eigenvalues + let mut eigval_series = Vec::, Dyn>>::with_capacity(time_res); + console::log_1(&"before engine eigenvalues".into()); + eigval_series.push(rand_mat.complex_eigenvalues()); + console::log_1(&"after engine eigenvalues".into()); + for _ in 1..time_res { + rand_mat = &rot_step * rand_mat; + eigval_series.push(rand_mat.complex_eigenvalues()); + } + eigval_series +} + +/* dynamic single float matrices */ +/*pub fn rand_eigval_series(time_res: usize) -> Vec, Dyn>> + where + N: ToTypenum + DimName + DimSub, + DefaultAllocator: + Allocator + + Allocator + + Allocator<>::Output> + + Allocator>::Output> +{ + // initialize the random matrix + let dim = N::try_to_usize().unwrap(); + console::log_1(&format!("dimension {dim}").into()); + let mut rand_mat = DMatrix::::from_fn(dim, dim, |j, k| { + let n = j*dim + k; + (E as f32)*((n*n) as f32) % 2.0_f32 - 1.0_f32 + }) * (3.0_f32 / (dim as f32)).sqrt(); + + // initialize the rotation step + let mut rot_step = DMatrix::::identity(dim, dim); + let max_freq = 4; + for n in (0..dim).step_by(2) { + let ang = (PI as f32) * ((n % max_freq) as f32) / (time_res as f32); + let ang_cos = ang.cos(); + let ang_sin = ang.sin(); + rot_step[(n, n)] = ang_cos; + rot_step[(n+1, n)] = ang_sin; + rot_step[(n, n+1)] = -ang_sin; + rot_step[(n+1, n+1)] = ang_cos; + } + + // find the eigenvalues + let mut eigval_series = Vec::, Dyn>>::with_capacity(time_res); + console::log_1(&"before engine eigenvalues".into()); + eigval_series.push(rand_mat.complex_eigenvalues()); + console::log_1(&"after engine eigenvalues".into()); + for _ in 1..time_res { + rand_mat = &rot_step * rand_mat; + eigval_series.push(rand_mat.complex_eigenvalues()); + } + eigval_series +}*/ \ No newline at end of file diff --git a/lang-trials/rust-benchmark/src/main.rs b/lang-trials/rust-benchmark/src/main.rs new file mode 100644 index 0000000..7143e20 --- /dev/null +++ b/lang-trials/rust-benchmark/src/main.rs @@ -0,0 +1,86 @@ +use nalgebra::*; +use std::f64::consts::PI as PI; +use sycamore::{prelude::*, rt::{JsCast, JsValue}}; +use web_sys::{console, window}; + +mod engine; + +fn main() { + // set up a config option that forwards panic messages to `console.error` + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); + + /*console::log_1(&"before test schur 60".into());*/ + /*let test_rand_mat = OMatrix::::identity();*/ + /*let test_rot_step = OMatrix::::identity();*/ + /*let test_schur = test_rand_mat.schur(); + console::log_1(&format!("after test schur").into()); + let test_eigvals = test_schur.complex_eigenvalues(); + console::log_1(&format!("after test eigenvalues").into());*/ + + sycamore::render(|| { + let time_res: usize = 100; + let time_step = create_signal(0.0); + let run_time_report = create_signal(-1.0); + let display = create_node_ref(); + + on_mount(move || { + let performance = window().unwrap().performance().unwrap(); + let start_time = performance.now(); + let eigval_series = engine::rand_eigval_series::(time_res); + let run_time = performance.now() - start_time; + run_time_report.set(run_time); + + let canvas = display + .get::() + .unchecked_into::(); + let ctx = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + ctx.set_fill_style(&JsValue::from("white")); + + create_effect(move || { + // center and normalize the coordinate system + let width = canvas.width() as f64; + let height = canvas.height() as f64; + ctx.set_transform(1.0, 0.0, 0.0, -1.0, 0.5*width, 0.5*height).unwrap(); + + // clear the previous frame + ctx.clear_rect(-0.5*width, -0.5*width, width, height); + + // find the resolution + const R_DISP: f64 = 1.5; + let res = width / (2.0*R_DISP); + + // draw the eigenvalues + let eigvals = &eigval_series[time_step.get() as usize]; + for n in 0..eigvals.len() { + ctx.begin_path(); + ctx.arc( + /* typecast only needed for single float version */ + res * f64::from(eigvals[n].re), + res * f64::from(eigvals[n].im), + 3.0, + 0.0, 2.0*PI + ).unwrap(); + ctx.fill(); + } + }); + }); + + view! { + div(id="app") { + div { (run_time_report.get()) " ms" } + canvas(ref=display, width="600", height="600") + input( + type="range", + max=(time_res - 1).to_string(), + bind:valueAsNumber=time_step + ) + } + } + }); +} \ No newline at end of file