From bb2db615908142640ddf8876c3305adbcc7c6fdf Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Mon, 8 Jun 2026 16:29:02 +0200 Subject: [PATCH 1/2] add mandelbrot set benchmark --- Cargo.lock | 4 ++ Cargo.toml | 1 + cases/mandelbrot/Cargo.toml | 14 +++++++ cases/mandelbrot/lib.rs | 79 ++++++++++++++++++++++++++++++++++++ cases/mandelbrot/out.wasm | Bin 0 -> 15311 bytes 5 files changed, 98 insertions(+) create mode 100644 cases/mandelbrot/Cargo.toml create mode 100644 cases/mandelbrot/lib.rs create mode 100644 cases/mandelbrot/out.wasm diff --git a/Cargo.lock b/Cargo.lock index 33db1df..4ac56a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -867,6 +867,10 @@ dependencies = [ "serde_json", ] +[[package]] +name = "wasmi_benches_mandelbrot" +version = "0.0.0" + [[package]] name = "wasmi_benches_matrix_mul" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index b5e58c4..7a2bc22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "cases/word_count", "cases/compression", "cases/json_parse", + "cases/mandelbrot", ] resolver = "2" diff --git a/cases/mandelbrot/Cargo.toml b/cases/mandelbrot/Cargo.toml new file mode 100644 index 0000000..d4b1fa0 --- /dev/null +++ b/cases/mandelbrot/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "wasmi_benches_mandelbrot" +version.workspace = true +publish.workspace = true +authors.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true + +[lib] +path = "lib.rs" +crate-type = ["cdylib"] + +[dependencies] diff --git a/cases/mandelbrot/lib.rs b/cases/mandelbrot/lib.rs new file mode 100644 index 0000000..05c1b0c --- /dev/null +++ b/cases/mandelbrot/lib.rs @@ -0,0 +1,79 @@ +extern crate alloc; + +use alloc::{boxed::Box, vec}; + +/// Maximum number of escape-time iterations evaluated per pixel. +/// +/// Pixels inside the Mandelbrot set never escape and therefore run the full +/// budget, which is what makes this benchmark compute bound. +const MAX_ITER: u32 = 1000; + +/// Left edge of the rendered region on the real axis. +const MIN_X: f64 = -2.0; +/// Bottom edge of the rendered region on the imaginary axis. +const MIN_Y: f64 = -1.25; +/// Side length of the (square) rendered region in the complex plane. +/// +/// The region spans `[-2.0, 0.5] × [-1.25, 1.25]`, a 2.5 × 2.5 window centered +/// on `(-0.75, 0.0)` that captures the whole classic Mandelbrot shape. Keeping +/// it square means each pixel maps to a square cell for any `size`. +const SPAN: f64 = 2.5; + +#[repr(C)] +pub struct MandelbrotData { + /// Per-pixel escape iteration counts, laid out row-major as `size × size`. + /// + /// There is no random input to seed: the image is fully determined by the + /// fixed view region and [`MAX_ITER`], so the buffer is simply (re)computed + /// from the pixel coordinates on every [`run`]. + iterations: Box<[u32]>, + /// Width and height of the (quadratic) render area in pixels. + size: usize, +} + +#[unsafe(no_mangle)] +pub extern "C" fn setup(size: usize) -> Box { + let pixels = size.checked_mul(size).expect("render area overflows usize"); + Box::new(MandelbrotData { + iterations: vec![0; pixels].into_boxed_slice(), + size, + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn teardown(_: Box) {} + +#[unsafe(no_mangle)] +pub extern "C" fn run(data: &mut MandelbrotData) { + let size = data.size; + // Distance between adjacent pixels in the complex plane. + let step = SPAN / size as f64; + for py in 0..size { + let cy = MIN_Y + py as f64 * step; + let row = &mut data.iterations[py * size..(py + 1) * size]; + for (px, cell) in row.iter_mut().enumerate() { + let cx = MIN_X + px as f64 * step; + // Escape-time iteration of z := z² + c starting from z = 0. + let mut zx = 0.0; + let mut zy = 0.0; + let mut iter = 0; + while iter < MAX_ITER { + let zx2 = zx * zx; + let zy2 = zy * zy; + if zx2 + zy2 > 4.0 { + break; + } + zy = 2.0 * zx * zy + cy; + zx = zx2 - zy2 + cx; + iter += 1; + } + *cell = iter; + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn output(data: &MandelbrotData) -> u64 { + // Sum every escape count so the optimizer cannot elide the computation. + data.iterations.iter().map(|&it| it as u64).sum() +} diff --git a/cases/mandelbrot/out.wasm b/cases/mandelbrot/out.wasm new file mode 100644 index 0000000000000000000000000000000000000000..325678a04a46f3183963ff07e06ea8d8c119c597 GIT binary patch literal 15311 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 F{{}-i;oSfL literal 0 HcmV?d00001 From df90d2dff9cf067d232af0aaff78f83bf9c91f19 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Mon, 8 Jun 2026 16:29:10 +0200 Subject: [PATCH 2/2] add spectralnorm benchmark --- Cargo.lock | 4 + Cargo.toml | 1 + cases/spectralnorm/Cargo.toml | 12 +++ cases/spectralnorm/lib.rs | 140 ++++++++++++++++++++++++++++++++++ cases/spectralnorm/out.wasm | Bin 0 -> 16163 bytes 5 files changed, 157 insertions(+) create mode 100644 cases/spectralnorm/Cargo.toml create mode 100644 cases/spectralnorm/lib.rs create mode 100644 cases/spectralnorm/out.wasm diff --git a/Cargo.lock b/Cargo.lock index 4ac56a0..dffd5e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,10 @@ version = "0.0.0" name = "wasmi_benches_sort" version = "0.0.0" +[[package]] +name = "wasmi_benches_spectralnorm" +version = "0.0.0" + [[package]] name = "wasmi_benches_tiny_keccak" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 7a2bc22..aeac44c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "cases/word_count", "cases/compression", "cases/json_parse", + "cases/spectralnorm", "cases/mandelbrot", ] resolver = "2" diff --git a/cases/spectralnorm/Cargo.toml b/cases/spectralnorm/Cargo.toml new file mode 100644 index 0000000..d8646cc --- /dev/null +++ b/cases/spectralnorm/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wasmi_benches_spectralnorm" +version.workspace = true +publish.workspace = true +authors.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true + +[lib] +path = "lib.rs" +crate-type = ["cdylib"] diff --git a/cases/spectralnorm/lib.rs b/cases/spectralnorm/lib.rs new file mode 100644 index 0000000..44ebd9e --- /dev/null +++ b/cases/spectralnorm/lib.rs @@ -0,0 +1,140 @@ +extern crate alloc; + +use alloc::{boxed::Box, vec, vec::Vec}; + +/// Number of power-iteration steps performed per `run`. +/// +/// This is the classic constant used by the "spectral norm" benchmark and is +/// enough for the dominant eigenvalue estimate to converge. +const ITERATIONS: usize = 10; + +#[repr(C)] +pub struct SpectralNormData { + /// Working vector (left operand of the power iteration). + u: Box<[f64]>, + /// Working vector (right operand of the power iteration). + v: Box<[f64]>, + /// Scratch space holding `A * x` between the two half-steps. + tmp: Box<[f64]>, + /// Randomized starting vector, copied into `u` at the start of each `run` + /// so that every run performs identical, deterministic work. + u_init: Box<[f64]>, + /// Estimated spectral norm from the last run. + result: f64, +} + +/// Deterministic LCG, identical in spirit to the one used by the `nbody` case. +struct Lcg { + state: u64, +} + +impl Lcg { + fn new(seed: u64) -> Self { + Self { state: seed } + } + + fn next_u64(&mut self) -> u64 { + self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1); + self.state + } + + fn next_f64(&mut self) -> f64 { + const SCALE: f64 = 1.0 / ((1u64 << 53) as f64); + ((self.next_u64() >> 11) as f64) * SCALE + } + + fn range(&mut self, min: f64, max: f64) -> f64 { + min + (max - min) * self.next_f64() + } +} + +/// Element `A(i, j)` of the infinite symmetric matrix used by the benchmark. +/// +/// The denominator is computed with integer arithmetic (as in the canonical +/// "spectral norm" benchmark) before a single conversion to `f64`. We use `u64` +/// rather than `usize` on purpose: on `wasm32` `usize` is 32-bit, so the +/// `ij * (ij + 1)` product would silently overflow for larger dimensions. +#[inline] +fn matrix(i: usize, j: usize) -> f64 { + let i = i as u64; + let j = j as u64; + let ij = i + j; + 1.0 / ((ij * (ij + 1) / 2 + i + 1) as f64) +} + +/// Computes `out = A * src`. +fn mul_a(src: &[f64], out: &mut [f64]) { + let n = src.len(); + for (i, out_i) in out.iter_mut().enumerate() { + let mut sum = 0.0; + for (j, &s) in src.iter().enumerate().take(n) { + sum += matrix(i, j) * s; + } + *out_i = sum; + } +} + +/// Computes `out = Aᵀ * src` (using `A(j, i)` instead of `A(i, j)`). +fn mul_at(src: &[f64], out: &mut [f64]) { + let n = src.len(); + for (i, out_i) in out.iter_mut().enumerate() { + let mut sum = 0.0; + for (j, &s) in src.iter().enumerate().take(n) { + sum += matrix(j, i) * s; + } + *out_i = sum; + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn setup(len: usize) -> Box { + let mut rng = Lcg::new(len as u64); + // Randomized, strictly positive starting vector. Any vector that is not + // orthogonal to the dominant eigenvector converges, so the exact values do + // not matter; positivity simply guarantees a well-behaved start. + let u_init: Vec = (0..len).map(|_| rng.range(0.5, 1.5)).collect(); + Box::new(SpectralNormData { + u: vec![0.0; len].into_boxed_slice(), + v: vec![0.0; len].into_boxed_slice(), + tmp: vec![0.0; len].into_boxed_slice(), + u_init: u_init.into_boxed_slice(), + result: 0.0, + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn teardown(_: Box) {} + +#[unsafe(no_mangle)] +pub extern "C" fn output(data: &SpectralNormData) -> f64 { + data.result +} + +#[unsafe(no_mangle)] +pub extern "C" fn run(data: &mut SpectralNormData) { + // Reset the working vector to the randomized start so each run is identical. + data.u.copy_from_slice(&data.u_init); + + let u: &mut [f64] = &mut data.u; + let v: &mut [f64] = &mut data.v; + let tmp: &mut [f64] = &mut data.tmp; + // Power iteration: repeatedly apply `AᵀA` to refine the dominant eigenvector. + for _ in 0..ITERATIONS { + // v = AᵀA * u + mul_a(u, tmp); + mul_at(tmp, v); + // u = AᵀA * v + mul_a(v, tmp); + mul_at(tmp, u); + } + + // After the loop `u = AᵀA·v`, so the Rayleigh quotient + // ||A||₂ ≈ sqrt(uᵀv / vᵀv) estimates the largest singular value. + let mut v_bv = 0.0; + let mut vv = 0.0; + for (&ui, &vi) in u.iter().zip(v.iter()) { + v_bv += ui * vi; + vv += vi * vi; + } + data.result = (v_bv / vv).sqrt(); +} diff --git a/cases/spectralnorm/out.wasm b/cases/spectralnorm/out.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e614814cb5c610a4a11d24c6f4450159691b28cb GIT binary patch literal 16163 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 IlsN