From 6b96242a292e398c88590c4342b44ebb1ab69509 Mon Sep 17 00:00:00 2001 From: Mark Nefedov Date: Fri, 14 Aug 2026 21:27:30 +0300 Subject: [PATCH 1/2] Update wide to 1.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a80cba..13299e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ num-traits = { version = "0.2.11", default-features = false } approx = { version = "0.5", default-features = false } decimal = { version = "2.0", default-features = false, optional = true } num-complex = { version = "0.4", default-features = false } -wide = { version = "1.5", default-features = false, optional = true } +wide = { version = "1.6", default-features = false, optional = true } fixed = { version = "1", optional = true } cordic = { version = "0.1", optional = true } rand = { version = "0.10", optional = true } From a226a07f9fbf389a9ea228bacdfc21863281c309 Mon Sep 17 00:00:00 2001 From: Mark Nefedov Date: Fri, 14 Aug 2026 22:20:37 +0300 Subject: [PATCH 2/2] Add criterion micro-benchmarks for the wide SIMD wrappers Covers extract/replace lane access, map_lanes-based math ops, horizontal reductions, boolean mask ops, and raw wide ceilings. --- Cargo.toml | 9 ++ benches/wide_ops.rs | 267 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 benches/wide_ops.rs diff --git a/Cargo.toml b/Cargo.toml index 13299e4..ff38c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,5 +36,14 @@ serde = { version = "1", default-features = false, optional = true } rkyv = { version = "0.7", optional = true } libm_force = { package = "libm", version = "0.2.15", optional = true } +[dev-dependencies] +criterion = "0.8" +wide = { version = "1.6" } + +[[bench]] +name = "wide_ops" +harness = false +required-features = ["std"] + [package.metadata.docs.rs] all-features = true diff --git a/benches/wide_ops.rs b/benches/wide_ops.rs new file mode 100644 index 0000000..6c063e4 --- /dev/null +++ b/benches/wide_ops.rs @@ -0,0 +1,267 @@ +//! Criterion micro-benchmarks for the `wide`-backed SIMD wrappers. +//! +//! Each group targets a finding from the 2026-08 optimization audit: +//! - `extract_replace`: per-lane access round-tripping the whole vector (S2) +//! - `map_lanes_ops`: lane-by-lane math with native `wide` equivalents (S1) +//! - `horizontal`: `simd_horizontal_sum` (native) vs `_product` (per-lane) (S4) +//! - `bool_ops`: `bitmask`/`all`/`any` lowering (S3) +//! - `native_ceiling`: the raw `wide` ops the swaps would delegate to. + +use criterion::{criterion_group, criterion_main, Criterion}; +use simba::simd::{ + SimdBool, SimdComplexField, SimdPartialOrd, SimdValue, WideF32x4, WideF32x8, WideF64x4, +}; +use std::hint::black_box; +use std::time::Duration; + +const N: usize = 1024; + +fn f32x4_data() -> Vec { + (0..N) + .map(|i| { + let base = (i as f32) * 0.37 + 0.11; + WideF32x4::from_arr([base, base + 0.25, base + 0.5, base + 0.75]) + }) + .collect() +} + +fn f64x4_data() -> Vec { + (0..N) + .map(|i| { + let base = (i as f64) * 0.37 + 0.11; + WideF64x4::from_arr([base, base + 0.25, base + 0.5, base + 0.75]) + }) + .collect() +} + +fn f32x8_data() -> Vec { + (0..N) + .map(|i| { + let base = (i as f32) * 0.37 + 0.11; + WideF32x8::from_arr([ + base, + base + 0.125, + base + 0.25, + base + 0.375, + base + 0.5, + base + 0.625, + base + 0.75, + base + 0.875, + ]) + }) + .collect() +} + +fn bench_extract_replace(c: &mut Criterion) { + let data4 = f32x4_data(); + let data64 = f64x4_data(); + let data8 = f32x8_data(); + + let mut g = c.benchmark_group("extract_replace"); + g.bench_function("WideF32x4::extract_all_lanes", |b| { + b.iter(|| { + let mut acc = 0.0f32; + for v in &data4 { + for i in 0..4 { + acc += v.extract(i); + } + } + black_box(acc) + }) + }); + g.bench_function("WideF32x8::extract_all_lanes", |b| { + b.iter(|| { + let mut acc = 0.0f32; + for v in &data8 { + for i in 0..8 { + acc += v.extract(i); + } + } + black_box(acc) + }) + }); + g.bench_function("WideF64x4::extract_all_lanes", |b| { + b.iter(|| { + let mut acc = 0.0f64; + for v in &data64 { + for i in 0..4 { + acc += v.extract(i); + } + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::replace_one_lane", |b| { + b.iter(|| { + let mut out = 0.0f32; + for (i, v) in data4.iter().enumerate() { + let mut v = *v; + v.replace(i & 3, 42.0); + out += v.extract(0); + } + black_box(out) + }) + }); + g.finish(); +} + +fn bench_map_lanes_ops(c: &mut Criterion) { + let data4 = f32x4_data(); + let data64 = f64x4_data(); + + let mut g = c.benchmark_group("map_lanes_ops"); + g.bench_function("WideF32x4::simd_floor", |b| { + b.iter(|| { + let mut acc = WideF32x4::ZERO; + for v in &data4 { + acc += v.simd_floor(); + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::simd_ln", |b| { + b.iter(|| { + let mut acc = WideF32x4::ZERO; + for v in &data4 { + acc += v.simd_ln(); + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::simd_tan", |b| { + b.iter(|| { + let mut acc = WideF32x4::ZERO; + for v in &data4 { + acc += v.simd_tan(); + } + black_box(acc) + }) + }); + g.bench_function("WideF64x4::simd_floor", |b| { + b.iter(|| { + let mut acc = WideF64x4::ZERO; + for v in &data64 { + acc += v.simd_floor(); + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::map_lanes_mul", |b| { + b.iter(|| { + let mut acc = WideF32x4::ZERO; + for v in &data4 { + acc += v.map_lanes(|e| e * 1.5); + } + black_box(acc) + }) + }); + g.finish(); +} + +fn bench_horizontal(c: &mut Criterion) { + let data4 = f32x4_data(); + + let mut g = c.benchmark_group("horizontal"); + g.bench_function("WideF32x4::simd_horizontal_sum", |b| { + b.iter(|| { + let mut acc = 0.0f32; + for v in &data4 { + acc += v.simd_horizontal_sum(); + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::simd_horizontal_product", |b| { + b.iter(|| { + let mut acc = 0.0f32; + for v in &data4 { + acc += v.simd_horizontal_product(); + } + black_box(acc) + }) + }); + g.finish(); +} + +fn bench_bool_ops(c: &mut Criterion) { + let data4 = f32x4_data(); + let threshold = WideF32x4::splat(189.5); + + let mut g = c.benchmark_group("bool_ops"); + g.bench_function("WideF32x4::cmp_bitmask", |b| { + b.iter(|| { + let mut acc = 0u64; + for v in &data4 { + acc ^= v.simd_gt(threshold).bitmask(); + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::cmp_and", |b| { + b.iter(|| { + let mut acc = false; + for v in &data4 { + acc ^= v.simd_gt(threshold).and(); + } + black_box(acc) + }) + }); + g.bench_function("WideF32x4::cmp_or", |b| { + b.iter(|| { + let mut acc = false; + for v in &data4 { + acc ^= v.simd_gt(threshold).or(); + } + black_box(acc) + }) + }); + g.finish(); +} + +fn bench_native_ceiling(c: &mut Criterion) { + let raw4: Vec = f32x4_data().iter().map(|v| v.0).collect(); + + let mut g = c.benchmark_group("native_ceiling"); + g.bench_function("wide::f32x4::floor", |b| { + b.iter(|| { + let mut acc = wide::f32x4::ZERO; + for v in &raw4 { + acc += v.floor(); + } + black_box(acc) + }) + }); + g.bench_function("wide::f32x4::reduce_add", |b| { + b.iter(|| { + let mut acc = 0.0f32; + for v in &raw4 { + acc += v.reduce_add(); + } + black_box(acc) + }) + }); + g.bench_function("wide::f32x4::reduce_mul", |b| { + b.iter(|| { + let mut acc = 0.0f32; + for v in &raw4 { + acc += v.reduce_mul(); + } + black_box(acc) + }) + }); + g.finish(); +} + +fn config() -> Criterion { + Criterion::default() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(2)) + .sample_size(60) +} + +criterion_group! { + name = benches; + config = config(); + targets = bench_extract_replace, bench_map_lanes_ops, bench_horizontal, bench_bool_ops, bench_native_ceiling +} +criterion_main!(benches);