Files
wifi-densepose/crates/ruvector-solver/benches/solver_baseline.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

194 lines
6.6 KiB
Rust

//! Baseline benchmarks for dense and sparse matrix-vector operations.
//!
//! These benchmarks establish performance baselines for the core linear algebra
//! primitives used throughout the solver crate: naive dense matrix-vector
//! multiply and CSR sparse matrix-vector multiply (SpMV).
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::time::Duration;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use ruvector_solver::types::CsrMatrix;
// ---------------------------------------------------------------------------
// Helpers: deterministic random data generation
// ---------------------------------------------------------------------------
/// Generate a dense matrix stored as a flat row-major `Vec<f32>`.
///
/// Uses a deterministic seed so benchmark results are reproducible across runs.
fn random_dense_matrix(rows: usize, cols: usize, seed: u64) -> Vec<f32> {
let mut rng = StdRng::seed_from_u64(seed);
(0..rows * cols).map(|_| rng.gen_range(-1.0..1.0)).collect()
}
/// Generate a random CSR matrix with approximately `density` fraction of
/// non-zero entries.
///
/// The matrix is square (`n x n`). Each entry in the upper triangle is
/// included independently with probability `density`, then mirrored to the
/// lower triangle for symmetry. Diagonal entries are always present and set
/// to a value ensuring strict diagonal dominance.
fn random_csr_matrix(n: usize, density: f64, seed: u64) -> CsrMatrix<f32> {
let mut rng = StdRng::seed_from_u64(seed);
let mut entries: Vec<(usize, usize, f32)> = Vec::new();
// Off-diagonal entries (symmetric).
for i in 0..n {
for j in (i + 1)..n {
if rng.gen::<f64>() < density {
let val: f32 = rng.gen_range(-0.5..0.5);
entries.push((i, j, val));
entries.push((j, i, val));
}
}
}
// Build row-wise absolute sums for diagonal dominance.
let mut row_abs_sums = vec![0.0f32; n];
for &(r, _c, v) in &entries {
row_abs_sums[r] += v.abs();
}
// Diagonal entries: ensure diagonal dominance for solver stability.
for i in 0..n {
entries.push((i, i, row_abs_sums[i] + 1.0));
}
CsrMatrix::<f32>::from_coo(n, n, entries)
}
/// Generate a random vector of length `n` with values in [-1, 1].
fn random_vector(n: usize, seed: u64) -> Vec<f32> {
let mut rng = StdRng::seed_from_u64(seed);
(0..n).map(|_| rng.gen_range(-1.0..1.0)).collect()
}
// ---------------------------------------------------------------------------
// Dense matrix-vector multiply (naive baseline)
// ---------------------------------------------------------------------------
/// Naive dense matrix-vector multiply: `y = A * x`.
///
/// `a` is stored in row-major order with dimensions `rows x cols`.
#[inline(never)]
fn dense_matvec(a: &[f32], x: &[f32], y: &mut [f32], rows: usize, cols: usize) {
for i in 0..rows {
let mut sum = 0.0f32;
let row_start = i * cols;
for j in 0..cols {
sum += a[row_start + j] * x[j];
}
y[i] = sum;
}
}
fn dense_matvec_baseline(c: &mut Criterion) {
let mut group = c.benchmark_group("dense_matvec");
group.warm_up_time(Duration::from_secs(3));
group.sample_size(100);
for size in [64, 256, 1024, 4096] {
let a = random_dense_matrix(size, size, 42);
let x = random_vector(size, 43);
let mut y = vec![0.0f32; size];
group.throughput(Throughput::Elements((size * size) as u64));
group.bench_with_input(BenchmarkId::new("naive", size), &size, |b, &n| {
b.iter(|| {
dense_matvec(
criterion::black_box(&a),
criterion::black_box(&x),
criterion::black_box(&mut y),
n,
n,
);
});
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// Sparse matrix-vector multiply (CSR SpMV)
// ---------------------------------------------------------------------------
fn sparse_spmv_baseline(c: &mut Criterion) {
let mut group = c.benchmark_group("sparse_spmv");
group.warm_up_time(Duration::from_secs(3));
group.sample_size(100);
for (n, density) in [(1000, 0.01), (1000, 0.05), (10_000, 0.01)] {
let csr = random_csr_matrix(n, density, 44);
let x = random_vector(n, 45);
let mut y = vec![0.0f32; n];
let label = format!("{}x{}_{:.0}pct", n, n, density * 100.0);
group.throughput(Throughput::Elements(csr.nnz() as u64));
group.bench_with_input(BenchmarkId::new(&label, n), &n, |b, _| {
b.iter(|| {
csr.spmv(criterion::black_box(&x), criterion::black_box(&mut y));
});
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// Dense vs sparse crossover
// ---------------------------------------------------------------------------
/// Benchmark that compares dense and sparse matvec at the same dimension
/// to help identify the crossover point where sparse becomes faster.
fn dense_vs_sparse_crossover(c: &mut Criterion) {
let mut group = c.benchmark_group("dense_vs_sparse_crossover");
group.warm_up_time(Duration::from_secs(3));
group.sample_size(100);
for size in [64, 128, 256, 512, 1024] {
let density = 0.05;
// Dense setup.
let a_dense = random_dense_matrix(size, size, 42);
let x = random_vector(size, 43);
let mut y_dense = vec![0.0f32; size];
group.throughput(Throughput::Elements((size * size) as u64));
group.bench_with_input(BenchmarkId::new("dense", size), &size, |b, &n| {
b.iter(|| {
dense_matvec(
criterion::black_box(&a_dense),
criterion::black_box(&x),
criterion::black_box(&mut y_dense),
n,
n,
);
});
});
// Sparse setup.
let csr = random_csr_matrix(size, density, 44);
let mut y_sparse = vec![0.0f32; size];
group.bench_with_input(BenchmarkId::new("sparse_5pct", size), &size, |b, _| {
b.iter(|| {
csr.spmv(
criterion::black_box(&x),
criterion::black_box(&mut y_sparse),
);
});
});
}
group.finish();
}
criterion_group!(
baselines,
dense_matvec_baseline,
sparse_spmv_baseline,
dense_vs_sparse_crossover
);
criterion_main!(baselines);