use std::sync::OnceLock;
use wide::{f32x4, f32x8, f64x2, f64x4};
#[derive(Clone, Copy, Debug)]
pub enum SimdLevel {
Scalar,
Sse,
Avx2,
Avx512,
}
static SIMD_LEVEL: OnceLock<SimdLevel> = OnceLock::new();
pub fn detect_simd_level() -> SimdLevel {
*SIMD_LEVEL.get_or_init(|| {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx512f") {
return SimdLevel::Avx512;
}
if is_x86_feature_detected!("avx2") {
return SimdLevel::Avx2;
}
if is_x86_feature_detected!("sse4.1") {
return SimdLevel::Sse;
}
return SimdLevel::Scalar;
}
#[cfg(target_arch = "aarch64")]
{
SimdLevel::Sse
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
SimdLevel::Scalar
}
})
}
#[inline(always)]
fn sum_squares_scalar_f32(a: &[f32]) -> f32 {
a.iter().map(|&x| x * x).sum()
}
#[inline(always)]
fn sum_squares_sse_f32(a: &[f32]) -> f32 {
let len = a.len();
let chunks = len / 4;
let mut acc = f32x4::ZERO;
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f32x4::from(*(a_ptr.add(i * 4) as *const [f32; 4]));
acc += va * va;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
sum += a[i] * a[i];
}
sum
}
#[inline(always)]
fn sum_squares_avx2_f32(a: &[f32]) -> f32 {
let len = a.len();
let chunks = len / 8;
let mut acc = f32x8::ZERO;
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f32x8::from(*(a_ptr.add(i * 8) as *const [f32; 8]));
acc += va * va;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 8)..len {
sum += a[i] * a[i];
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn sum_squares_avx512_f32(a: &[f32]) -> f32 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 16;
unsafe {
let mut acc = _mm512_setzero_ps();
for i in 0..chunks {
let va = _mm512_loadu_ps(a.as_ptr().add(i * 16));
acc = _mm512_fmadd_ps(va, va, acc);
}
let mut sum = _mm512_reduce_add_ps(acc);
for i in (chunks * 16)..len {
sum += a[i] * a[i];
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn sum_squares_avx512_f32(a: &[f32]) -> f32 {
sum_squares_avx2_f32(a)
}
#[inline]
pub fn sum_squares_simd_f32(a: &[f32]) -> f32 {
match detect_simd_level() {
SimdLevel::Avx512 => sum_squares_avx512_f32(a),
SimdLevel::Avx2 => sum_squares_avx2_f32(a),
SimdLevel::Sse => sum_squares_sse_f32(a),
SimdLevel::Scalar => sum_squares_scalar_f32(a),
}
}
pub trait BixverseSimd:
Copy + Default + std::ops::AddAssign + std::ops::Mul<Output = Self>
{
fn bxv_dot_simd(a: &[Self], b: &[Self]) -> Self;
fn bxv_sum(x: &[Self]) -> Self;
fn bxv_sum_squared_deviation(x: &[Self], mean: Self) -> Self;
}
impl BixverseSimd for f32 {
#[inline]
fn bxv_dot_simd(a: &[f32], b: &[f32]) -> f32 {
dot_simd_f32(a, b)
}
#[inline]
fn bxv_sum(x: &[f32]) -> f32 {
sum_simd_f32(x)
}
#[inline]
fn bxv_sum_squared_deviation(x: &[f32], mean: f32) -> f32 {
sum_squared_dev_simd_f32(x, mean)
}
}
impl BixverseSimd for f64 {
#[inline]
fn bxv_dot_simd(a: &[f64], b: &[f64]) -> f64 {
dot_simd_f64(a, b)
}
#[inline]
fn bxv_sum(x: &[f64]) -> f64 {
sum_simd_f64(x)
}
#[inline]
fn bxv_sum_squared_deviation(x: &[f64], mean: f64) -> f64 {
sum_squared_dev_simd_f64(x, mean)
}
}
#[inline(always)]
fn dot_scalar_f32(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
}
#[inline(always)]
fn dot_sse_f32(a: &[f32], b: &[f32]) -> f32 {
let len = a.len();
let chunks = len / 4;
let mut acc = f32x4::ZERO;
unsafe {
let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();
for i in 0..chunks {
let va = f32x4::from(*(a_ptr.add(i * 4) as *const [f32; 4]));
let vb = f32x4::from(*(b_ptr.add(i * 4) as *const [f32; 4]));
acc += va * vb;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
sum += a[i] * b[i];
}
sum
}
#[inline(always)]
fn dot_avx2_f32(a: &[f32], b: &[f32]) -> f32 {
let len = a.len();
let chunks = len / 8;
let mut acc = f32x8::ZERO;
unsafe {
let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();
for i in 0..chunks {
let va = f32x8::from(*(a_ptr.add(i * 8) as *const [f32; 8]));
let vb = f32x8::from(*(b_ptr.add(i * 8) as *const [f32; 8]));
acc += va * vb;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 8)..len {
sum += a[i] * b[i];
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn dot_avx512_f32(a: &[f32], b: &[f32]) -> f32 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 16;
unsafe {
let mut acc = _mm512_setzero_ps();
for i in 0..chunks {
let va = _mm512_loadu_ps(a.as_ptr().add(i * 16));
let vb = _mm512_loadu_ps(b.as_ptr().add(i * 16));
acc = _mm512_fmadd_ps(va, vb, acc);
}
let mut sum = _mm512_reduce_add_ps(acc);
for i in (chunks * 16)..len {
sum += a[i] * b[i];
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn dot_avx512_f32(a: &[f32], b: &[f32]) -> f32 {
dot_avx2_f32(a, b)
}
#[inline]
pub fn dot_simd_f32(a: &[f32], b: &[f32]) -> f32 {
match detect_simd_level() {
SimdLevel::Avx512 => dot_avx512_f32(a, b),
SimdLevel::Avx2 => dot_avx2_f32(a, b),
SimdLevel::Sse => dot_sse_f32(a, b),
SimdLevel::Scalar => dot_scalar_f32(a, b),
}
}
#[inline(always)]
fn dot_scalar_f64(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
}
#[inline(always)]
fn dot_sse_f64(a: &[f64], b: &[f64]) -> f64 {
let len = a.len();
let chunks = len / 2;
let mut acc = f64x2::ZERO;
unsafe {
let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();
for i in 0..chunks {
let va = f64x2::from(*(a_ptr.add(i * 2) as *const [f64; 2]));
let vb = f64x2::from(*(b_ptr.add(i * 2) as *const [f64; 2]));
acc += va * vb;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 2)..len {
sum += a[i] * b[i];
}
sum
}
#[inline(always)]
fn dot_avx2_f64(a: &[f64], b: &[f64]) -> f64 {
let len = a.len();
let chunks = len / 4;
let mut acc = f64x4::ZERO;
unsafe {
let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();
for i in 0..chunks {
let va = f64x4::from(*(a_ptr.add(i * 4) as *const [f64; 4]));
let vb = f64x4::from(*(b_ptr.add(i * 4) as *const [f64; 4]));
acc += va * vb;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
sum += a[i] * b[i];
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn dot_avx512_f64(a: &[f64], b: &[f64]) -> f64 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 8;
unsafe {
let mut acc = _mm512_setzero_pd();
for i in 0..chunks {
let va = _mm512_loadu_pd(a.as_ptr().add(i * 8));
let vb = _mm512_loadu_pd(b.as_ptr().add(i * 8));
acc = _mm512_fmadd_pd(va, vb, acc);
}
let mut sum = _mm512_reduce_add_pd(acc);
for i in (chunks * 8)..len {
sum += a[i] * b[i];
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn dot_avx512_f64(a: &[f64], b: &[f64]) -> f64 {
dot_avx2_f64(a, b)
}
#[inline]
pub fn dot_simd_f64(a: &[f64], b: &[f64]) -> f64 {
match detect_simd_level() {
SimdLevel::Avx512 => dot_avx512_f64(a, b),
SimdLevel::Avx2 => dot_avx2_f64(a, b),
SimdLevel::Sse => dot_sse_f64(a, b),
SimdLevel::Scalar => dot_scalar_f64(a, b),
}
}
#[inline(always)]
fn sum_scalar_f32(a: &[f32]) -> f32 {
a.iter().sum()
}
#[inline(always)]
fn sum_sse_f32(a: &[f32]) -> f32 {
let len = a.len();
let chunks = len / 4;
let mut acc = f32x4::ZERO;
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f32x4::from(*(a_ptr.add(i * 4) as *const [f32; 4]));
acc += va;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
sum += a[i];
}
sum
}
#[inline(always)]
fn sum_avx2_f32(a: &[f32]) -> f32 {
let len = a.len();
let chunks = len / 8;
let mut acc = f32x8::ZERO;
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f32x8::from(*(a_ptr.add(i * 8) as *const [f32; 8]));
acc += va;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 8)..len {
sum += a[i];
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn sum_avx512_f32(a: &[f32]) -> f32 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 16;
unsafe {
let mut acc = _mm512_setzero_ps();
for i in 0..chunks {
let va = _mm512_loadu_ps(a.as_ptr().add(i * 16));
acc = _mm512_add_ps(acc, va);
}
let mut sum = _mm512_reduce_add_ps(acc);
for i in (chunks * 16)..len {
sum += a[i];
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn sum_avx512_f32(a: &[f32]) -> f32 {
sum_avx2_f32(a)
}
#[inline]
pub fn sum_simd_f32(a: &[f32]) -> f32 {
match detect_simd_level() {
SimdLevel::Avx512 => sum_avx512_f32(a),
SimdLevel::Avx2 => sum_avx2_f32(a),
SimdLevel::Sse => sum_sse_f32(a),
SimdLevel::Scalar => sum_scalar_f32(a),
}
}
#[inline(always)]
fn sum_scalar_f64(a: &[f64]) -> f64 {
a.iter().sum()
}
#[inline(always)]
fn sum_sse_f64(a: &[f64]) -> f64 {
let len = a.len();
let chunks = len / 2;
let mut acc = f64x2::ZERO;
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f64x2::from(*(a_ptr.add(i * 2) as *const [f64; 2]));
acc += va;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 2)..len {
sum += a[i];
}
sum
}
#[inline(always)]
fn sum_avx2_f64(a: &[f64]) -> f64 {
let len = a.len();
let chunks = len / 4;
let mut acc = f64x4::ZERO;
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f64x4::from(*(a_ptr.add(i * 4) as *const [f64; 4]));
acc += va;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
sum += a[i];
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn sum_avx512_f64(a: &[f64]) -> f64 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 8;
unsafe {
let mut acc = _mm512_setzero_pd();
for i in 0..chunks {
let va = _mm512_loadu_pd(a.as_ptr().add(i * 8));
acc = _mm512_add_pd(acc, va);
}
let mut sum = _mm512_reduce_add_pd(acc);
for i in (chunks * 8)..len {
sum += a[i];
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn sum_avx512_f64(a: &[f64]) -> f64 {
sum_avx2_f64(a)
}
#[inline]
pub fn sum_simd_f64(a: &[f64]) -> f64 {
match detect_simd_level() {
SimdLevel::Avx512 => sum_avx512_f64(a),
SimdLevel::Avx2 => sum_avx2_f64(a),
SimdLevel::Sse => sum_sse_f64(a),
SimdLevel::Scalar => sum_scalar_f64(a),
}
}
#[inline(always)]
fn sum_squared_dev_scalar_f32(a: &[f32], mean: f32) -> f32 {
a.iter().map(|&x| (x - mean).powi(2)).sum::<f32>()
}
#[inline(always)]
fn sum_squared_dev_sse_f32(a: &[f32], mean: f32) -> f32 {
let len = a.len();
let chunks = len / 4;
let mut acc = f32x4::ZERO;
let mean_vec = f32x4::splat(mean);
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f32x4::from(*(a_ptr.add(i * 4) as *const [f32; 4]));
let diff = va - mean_vec;
acc += diff * diff;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
let diff = a[i] - mean;
sum += diff * diff;
}
sum
}
#[inline(always)]
fn sum_squared_dev_avx2_f32(a: &[f32], mean: f32) -> f32 {
let len = a.len();
let chunks = len / 8;
let mut acc = f32x8::ZERO;
let mean_vec = f32x8::splat(mean);
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f32x8::from(*(a_ptr.add(i * 8) as *const [f32; 8]));
let diff = va - mean_vec;
acc += diff * diff;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 8)..len {
let diff = a[i] - mean;
sum += diff * diff;
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn sum_squared_dev_avx512_f32(a: &[f32], mean: f32) -> f32 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 16;
unsafe {
let mut acc = _mm512_setzero_ps();
let mean_vec = _mm512_set1_ps(mean);
for i in 0..chunks {
let va = _mm512_loadu_ps(a.as_ptr().add(i * 16));
let diff = _mm512_sub_ps(va, mean_vec);
acc = _mm512_fmadd_ps(diff, diff, acc);
}
let mut sum = _mm512_reduce_add_ps(acc);
for i in (chunks * 16)..len {
let diff = a[i] - mean;
sum += diff * diff;
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn sum_squared_dev_avx512_f32(a: &[f32], mean: f32) -> f32 {
sum_squared_dev_avx2_f32(a, mean)
}
#[inline]
pub fn sum_squared_dev_simd_f32(a: &[f32], mean: f32) -> f32 {
match detect_simd_level() {
SimdLevel::Avx512 => sum_squared_dev_avx512_f32(a, mean),
SimdLevel::Avx2 => sum_squared_dev_avx2_f32(a, mean),
SimdLevel::Sse => sum_squared_dev_sse_f32(a, mean),
SimdLevel::Scalar => sum_squared_dev_scalar_f32(a, mean),
}
}
#[inline(always)]
fn sum_squared_dev_scalar_f64(a: &[f64], mean: f64) -> f64 {
a.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
}
#[inline(always)]
fn sum_squared_dev_sse_f64(a: &[f64], mean: f64) -> f64 {
let len = a.len();
let chunks = len / 2;
let mut acc = f64x2::ZERO;
let mean_vec = f64x2::splat(mean);
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f64x2::from(*(a_ptr.add(i * 2) as *const [f64; 2]));
let diff = va - mean_vec;
acc += diff * diff;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 2)..len {
let diff = a[i] - mean;
sum += diff * diff;
}
sum
}
#[inline(always)]
fn sum_squared_dev_avx2_f64(a: &[f64], mean: f64) -> f64 {
let len = a.len();
let chunks = len / 4;
let mut acc = f64x4::ZERO;
let mean_vec = f64x4::splat(mean);
unsafe {
let a_ptr = a.as_ptr();
for i in 0..chunks {
let va = f64x4::from(*(a_ptr.add(i * 4) as *const [f64; 4]));
let diff = va - mean_vec;
acc += diff * diff;
}
}
let mut sum = acc.reduce_add();
for i in (chunks * 4)..len {
let diff = a[i] - mean;
sum += diff * diff;
}
sum
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
#[inline(always)]
fn sum_squared_dev_avx512_f64(a: &[f64], mean: f64) -> f64 {
use std::arch::x86_64::*;
let len = a.len();
let chunks = len / 8;
unsafe {
let mut acc = _mm512_setzero_pd();
let mean_vec = _mm512_set1_pd(mean);
for i in 0..chunks {
let va = _mm512_loadu_pd(a.as_ptr().add(i * 8));
let diff = _mm512_sub_pd(va, mean_vec);
acc = _mm512_fmadd_pd(diff, diff, acc);
}
let mut sum = _mm512_reduce_add_pd(acc);
for i in (chunks * 8)..len {
let diff = a[i] - mean;
sum += diff * diff;
}
sum
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
#[inline(always)]
fn sum_squared_dev_avx512_f64(a: &[f64], mean: f64) -> f64 {
sum_squared_dev_avx2_f64(a, mean)
}
#[inline]
pub fn sum_squared_dev_simd_f64(a: &[f64], mean: f64) -> f64 {
match detect_simd_level() {
SimdLevel::Avx512 => sum_squared_dev_avx512_f64(a, mean),
SimdLevel::Avx2 => sum_squared_dev_avx2_f64(a, mean),
SimdLevel::Sse => sum_squared_dev_sse_f64(a, mean),
SimdLevel::Scalar => sum_squared_dev_scalar_f64(a, mean),
}
}