From 2959bb376bb488bfe224c7e6a28771aa137efe27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 26 Jul 2026 17:43:06 +0200 Subject: [PATCH 1/3] perf: avoid libc call in WideF32xX::splat --- src/simd/wide_simd_impl.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/simd/wide_simd_impl.rs b/src/simd/wide_simd_impl.rs index c1257f5..ce6efa6 100644 --- a/src/simd/wide_simd_impl.rs +++ b/src/simd/wide_simd_impl.rs @@ -164,7 +164,9 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn splat(val: Self::Element) -> Self { - $WideF32xX(wide::$f32xX::from(val)) + // NOTE: we don’t use `wide::$f32xX::from(val)` because this results in + // an inefficient `memset_pattern16` libc call. + $WideF32xX(wide::$f32xX::new([val, $({ let _ = $ii; val }),+])) } #[inline(always)] @@ -727,7 +729,7 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn simd_copysign(self, sign: Self) -> Self { - let neg_zero = wide::$f32xX::from(-0.0); + let neg_zero = ::splat(-0.0).0; $WideF32xX((neg_zero & sign.0) | ((!neg_zero) & self.0)) } From f2b93782b96d3e30f8a307a102577544d5180a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Fri, 31 Jul 2026 19:34:17 +0200 Subject: [PATCH 2/3] =?UTF-8?q?feat:=E2=80=AFhave=20wide=20types=20use=20l?= =?UTF-8?q?ibm=20when=20libm=5Fforce=20is=20specified?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/simd/wide_simd_impl.rs | 88 +++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44c9534..5aded90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ cordic = { version = "0.1", optional = true } rand = { version = "0.10", optional = true } serde = { version = "1", default-features = false, optional = true } rkyv = { version = "0.7", optional = true } -libm_force = { package = "libm", version = "0.2", optional = true } +libm_force = { package = "libm", version = "0.2.15", optional = true } [package.metadata.docs.rs] all-features = true diff --git a/src/simd/wide_simd_impl.rs b/src/simd/wide_simd_impl.rs index ce6efa6..84bd9ef 100644 --- a/src/simd/wide_simd_impl.rs +++ b/src/simd/wide_simd_impl.rs @@ -3,7 +3,7 @@ //! Traits for SIMD values. -use crate::scalar::{ComplexField, Field, SubsetOf, SupersetOf}; +use crate::scalar::{RealField, ComplexField, Field, SubsetOf, SupersetOf}; use crate::simd::{ PrimitiveSimdValue, SimdBool, SimdComplexField, SimdPartialOrd, SimdRealField, SimdSigned, SimdValue, @@ -18,6 +18,10 @@ use std::{ RemAssign, Sub, SubAssign, }, }; + +// The comparison methods resolve through these traits inside the impl macros; the +// "unused import" lint can't see through the macro expansion. +#[allow(unused_imports, deprecated)] use wide::{CmpEq, CmpGe, CmpGt, CmpLe, CmpLt, CmpNe}; #[cfg(feature = "rkyv")] @@ -724,7 +728,7 @@ macro_rules! impl_wide_f32 ( impl SimdRealField for $WideF32xX { #[inline(always)] fn simd_atan2(self, other: Self) -> Self { - self.zip_map_lanes(other, |a, b| a.atan2(b)) + self.zip_map_lanes(other, <$f32 as RealField>::atan2) } #[inline(always)] @@ -936,12 +940,12 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn simd_powf(self, n: Self) -> Self { - self.zip_map_lanes(n, |e, n| e.powf(n)) + self.zip_map_lanes(n, <$f32 as ComplexField>::powf) } #[inline(always)] fn simd_powc(self, n: Self) -> Self { - self.zip_map_lanes(n, |e, n| e.powf(n)) + self.zip_map_lanes(n, <$f32 as ComplexField>::powf) } #[inline(always)] @@ -951,128 +955,160 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn simd_exp(self) -> Self { - self.map_lanes(|e| e.exp()) + self.map_lanes(<$f32 as ComplexField>::exp) } #[inline(always)] fn simd_exp2(self) -> Self { - self.map_lanes(|e| e.exp2()) + self.map_lanes(<$f32 as ComplexField>::exp2) } #[inline(always)] fn simd_exp_m1(self) -> Self { - self.map_lanes(|e| e.exp_m1()) + self.map_lanes(<$f32 as ComplexField>::exp_m1) } #[inline(always)] fn simd_ln_1p(self) -> Self { - self.map_lanes(|e| e.ln_1p()) + self.map_lanes(<$f32 as ComplexField>::ln_1p) } #[inline(always)] fn simd_ln(self) -> Self { - self.map_lanes(|e| e.ln()) + self.map_lanes(<$f32 as ComplexField>::ln) } #[inline(always)] fn simd_log(self, base: Self) -> Self { - self.zip_map_lanes(base, |e, b| e.log(b)) + // Same formula as `std`'s `log`, per-lane. + self.zip_map_lanes(base, |e, b| <$f32 as ComplexField>::ln(e) / <$f32 as ComplexField>::ln(b)) } #[inline(always)] fn simd_log2(self) -> Self { - self.map_lanes(|e| e.log2()) + self.map_lanes(<$f32 as ComplexField>::log2) } #[inline(always)] fn simd_log10(self) -> Self { - self.map_lanes(|e| e.log10()) + self.map_lanes(<$f32 as ComplexField>::log10) } #[inline(always)] fn simd_cbrt(self) -> Self { - self.map_lanes(|e| e.cbrt()) + self.map_lanes(<$f32 as ComplexField>::cbrt) } #[inline(always)] fn simd_hypot(self, other: Self) -> Self::SimdRealField { - self.zip_map_lanes(other, |e, o| e.hypot(o)) + self.zip_map_lanes(other, <$f32 as ComplexField>::hypot) } + // sin/cos/sin_cos keep wide's SIMD polynomial by default (it internally uses + // `mul_add`, fused on NEON but not baseline x86 — fine without a determinism + // requirement) and go per-lane through libm under `libm_force`. + #[cfg(not(feature = "libm_force"))] #[inline(always)] fn simd_sin(self) -> Self { $WideF32xX(self.0.sin()) } + #[cfg(feature = "libm_force")] + #[inline(always)] + fn simd_sin(self) -> Self { + self.map_lanes(<$f32 as ComplexField>::sin) + } + + #[cfg(not(feature = "libm_force"))] #[inline(always)] fn simd_cos(self) -> Self { $WideF32xX(self.0.cos()) } + #[cfg(feature = "libm_force")] + #[inline(always)] + fn simd_cos(self) -> Self { + self.map_lanes(<$f32 as ComplexField>::cos) + } + #[inline(always)] fn simd_tan(self) -> Self { - self.map_lanes(|e| e.tan()) + self.map_lanes(<$f32 as ComplexField>::tan) } #[inline(always)] fn simd_asin(self) -> Self { - self.map_lanes(|e| e.asin()) + self.map_lanes(<$f32 as ComplexField>::asin) } #[inline(always)] fn simd_acos(self) -> Self { - self.map_lanes(|e| e.acos()) + self.map_lanes(<$f32 as ComplexField>::acos) } #[inline(always)] fn simd_atan(self) -> Self { - self.map_lanes(|e| e.atan()) + self.map_lanes(<$f32 as ComplexField>::atan) } + #[cfg(not(feature = "libm_force"))] #[inline(always)] fn simd_sin_cos(self) -> (Self, Self) { let (sin, cos) = self.0.sin_cos(); ($WideF32xX(sin), $WideF32xX(cos)) } + #[cfg(feature = "libm_force")] + #[inline(always)] + fn simd_sin_cos(self) -> (Self, Self) { + let mut sin = self; + let mut cos = self; + for ii in 0..$lanes { + let (s, c) = <$f32 as ComplexField>::sin_cos(self.extract(ii)); + sin.replace(ii, s); + cos.replace(ii, c); + } + (sin, cos) + } + // #[inline(always] // fn simd_exp_m1(self) -> Self { - // $libm::exp_m1(self) + // <$f32 as ComplexField>::exp_m1(self) // } // // #[inline(always] // fn simd_ln_1p(self) -> Self { - // $libm::ln_1p(self) + // <$f32 as ComplexField>::ln_1p(self) // } // #[inline(always)] fn simd_sinh(self) -> Self { - self.map_lanes(|e| e.sinh()) + self.map_lanes(<$f32 as ComplexField>::sinh) } #[inline(always)] fn simd_cosh(self) -> Self { - self.map_lanes(|e| e.cosh()) + self.map_lanes(<$f32 as ComplexField>::cosh) } #[inline(always)] fn simd_tanh(self) -> Self { - self.map_lanes(|e| e.tanh()) + self.map_lanes(<$f32 as ComplexField>::tanh) } #[inline(always)] fn simd_asinh(self) -> Self { - self.map_lanes(|e| e.asinh()) + self.map_lanes(<$f32 as ComplexField>::asinh) } #[inline(always)] fn simd_acosh(self) -> Self { - self.map_lanes(|e| e.acosh()) + self.map_lanes(<$f32 as ComplexField>::acosh) } #[inline(always)] fn simd_atanh(self) -> Self { - self.map_lanes(|e| e.atanh()) + self.map_lanes(<$f32 as ComplexField>::atanh) } } From a32a2653b794d873a2bdcc6e5109a78d9596b250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 2 Aug 2026 15:50:06 +0200 Subject: [PATCH 3/3] fix fmt and clippy --- CHANGELOG | 8 ++++++++ Cargo.toml | 2 +- src/simd/wide_simd_impl.rs | 13 ++++--------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c073aa2..f1d726c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +## Unreleased +- Bump the minimum `wide` version to 1.6, and switch from the deprecated `blend`/`Cmp*` APIs to `select` and the + inherent `simd_*` comparison methods. +- The `WideF32xX`/`WideF64xX` math functions now go through `ComplexField`/`RealField` instead of the inherent `std` + methods, so they honor the `libm_force` feature. Under `libm_force`, `simd_sin`, `simd_cos` and `simd_sin_cos` are + computed per-lane with `libm` instead of `wide`'s SIMD polynomials. +- `SimdValue::splat` for the wide types no longer emits a `memset_pattern16` libc call. + ## Release v0.10.0 (24 May 2026) - Update the `rand` dependency to 0.10 (from 0.8). As a side-effect, the `Distribution for StandardUniform` implementations for SIMD types containing `isize`/`usize` now generate random values via `fill_bytes()`, because diff --git a/Cargo.toml b/Cargo.toml index 5aded90..b1f1278 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", 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 } diff --git a/src/simd/wide_simd_impl.rs b/src/simd/wide_simd_impl.rs index 84bd9ef..b92f25c 100644 --- a/src/simd/wide_simd_impl.rs +++ b/src/simd/wide_simd_impl.rs @@ -3,7 +3,7 @@ //! Traits for SIMD values. -use crate::scalar::{RealField, ComplexField, Field, SubsetOf, SupersetOf}; +use crate::scalar::{ComplexField, Field, RealField, SubsetOf, SupersetOf}; use crate::simd::{ PrimitiveSimdValue, SimdBool, SimdComplexField, SimdPartialOrd, SimdRealField, SimdSigned, SimdValue, @@ -19,11 +19,6 @@ use std::{ }, }; -// The comparison methods resolve through these traits inside the impl macros; the -// "unused import" lint can't see through the macro expansion. -#[allow(unused_imports, deprecated)] -use wide::{CmpEq, CmpGe, CmpGt, CmpLe, CmpLt, CmpNe}; - #[cfg(feature = "rkyv")] macro_rules! impl_rkyv { ($type:ty, $array:ty) => { @@ -199,7 +194,7 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn select(self, cond: Self::SimdBool, other: Self) -> Self { - $WideF32xX(cond.0.blend(self.0, other.0)) + $WideF32xX(cond.0.select(self.0, other.0)) } } @@ -245,7 +240,7 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn select(self, cond: Self::SimdBool, other: Self) -> Self { - $WideBoolF32xX(cond.0.blend(self.0, other.0)) + $WideBoolF32xX(cond.0.select(self.0, other.0)) } } @@ -869,7 +864,7 @@ macro_rules! impl_wide_f32 ( #[inline(always)] fn simd_to_exp(self) -> (Self::SimdRealField, Self) { let ge = self.0.simd_ge(Self::one().0); - let exp = ge.blend(Self::one().0, -Self::one().0); + let exp = ge.select(Self::one().0, -Self::one().0); ($WideF32xX(self.0 * exp), $WideF32xX(exp)) }