pub fn simd_sum_f64(data: &[f64]) -> f64 {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
return unsafe { simd_sum_f64_avx2(data) };
}
}
sum_skipnan_scalar(data)
}
pub fn simd_mean_f64(data: &[f64]) -> f64 {
if data.is_empty() {
return 0.0;
}
let (sum, count) = sum_count_skipnan_scalar(data);
if count == 0 {
return f64::NAN;
}
sum / count as f64
}
pub fn simd_min_f64(data: &[f64]) -> f64 {
min_skipnan_scalar(data)
}
pub fn simd_max_f64(data: &[f64]) -> f64 {
max_skipnan_scalar(data)
}
pub fn simd_sum_i64(data: &[i64]) -> i64 {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
return unsafe { simd_sum_i64_avx2(data) };
}
}
sum_i64_scalar(data)
}
pub fn simd_mean_i64(data: &[i64]) -> i64 {
if data.is_empty() {
return 0;
}
simd_sum_i64(data) / data.len() as i64
}
pub fn simd_min_i64(data: &[i64]) -> i64 {
data.iter().copied().min().unwrap_or(i64::MAX)
}
pub fn simd_max_i64(data: &[i64]) -> i64 {
data.iter().copied().max().unwrap_or(i64::MIN)
}
fn sum_skipnan_scalar(data: &[f64]) -> f64 {
let mut acc = [0.0f64; 4];
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
for (lane, &v) in acc.iter_mut().zip(chunk) {
*lane += if v.is_nan() { 0.0 } else { v };
}
}
let mut total = (acc[0] + acc[1]) + (acc[2] + acc[3]);
for &v in remainder {
if !v.is_nan() {
total += v;
}
}
total
}
fn sum_count_skipnan_scalar(data: &[f64]) -> (f64, usize) {
let mut acc = [0.0f64; 4];
let mut cnt = [0usize; 4];
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
for lane in 0..4 {
let v = chunk[lane];
let valid = !v.is_nan();
acc[lane] += if valid { v } else { 0.0 };
cnt[lane] += valid as usize;
}
}
let mut total = (acc[0] + acc[1]) + (acc[2] + acc[3]);
let mut count = (cnt[0] + cnt[1]) + (cnt[2] + cnt[3]);
for &v in remainder {
let valid = !v.is_nan();
total += if valid { v } else { 0.0 };
count += valid as usize;
}
(total, count)
}
fn min_skipnan_scalar(data: &[f64]) -> f64 {
let mut lanes = [f64::INFINITY; 4];
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
for (lane, &v) in lanes.iter_mut().zip(chunk) {
*lane = lane.min(v);
}
}
let mut result = lanes[0].min(lanes[1]).min(lanes[2]).min(lanes[3]);
for &v in remainder {
result = result.min(v);
}
result
}
fn max_skipnan_scalar(data: &[f64]) -> f64 {
let mut lanes = [f64::NEG_INFINITY; 4];
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
for (lane, &v) in lanes.iter_mut().zip(chunk) {
*lane = lane.max(v);
}
}
let mut result = lanes[0].max(lanes[1]).max(lanes[2]).max(lanes[3]);
for &v in remainder {
result = result.max(v);
}
result
}
fn sum_i64_scalar(data: &[i64]) -> i64 {
data.iter().copied().fold(0i64, i64::wrapping_add)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn simd_sum_f64_avx2(data: &[f64]) -> f64 {
use std::arch::x86_64::*;
let mut sum = _mm256_setzero_pd();
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let v = _mm256_loadu_pd(chunk.as_ptr());
let ord = _mm256_cmp_pd(v, v, _CMP_ORD_Q);
let cleaned = _mm256_and_pd(v, ord);
sum = _mm256_add_pd(sum, cleaned);
}
let mut lanes = [0.0f64; 4];
_mm256_storeu_pd(lanes.as_mut_ptr(), sum);
let mut total = (lanes[0] + lanes[1]) + (lanes[2] + lanes[3]);
for &v in remainder {
if !v.is_nan() {
total += v;
}
}
total
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn simd_sum_i64_avx2(data: &[i64]) -> i64 {
use std::arch::x86_64::*;
let mut sum = _mm256_setzero_si256();
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let v = _mm256_loadu_si256(chunk.as_ptr() as *const __m256i);
sum = _mm256_add_epi64(sum, v);
}
let mut lanes = [0i64; 4];
_mm256_storeu_si256(lanes.as_mut_ptr() as *mut __m256i, sum);
let mut total = lanes[0]
.wrapping_add(lanes[1])
.wrapping_add(lanes[2])
.wrapping_add(lanes[3]);
for &v in remainder {
total = total.wrapping_add(v);
}
total
}
pub fn simd_available() -> bool {
#[cfg(target_arch = "x86_64")]
{
is_x86_feature_detected!("sse2")
}
#[cfg(not(target_arch = "x86_64"))]
{
false
}
}
pub fn avx2_available() -> bool {
#[cfg(target_arch = "x86_64")]
{
is_x86_feature_detected!("avx2")
}
#[cfg(not(target_arch = "x86_64"))]
{
false
}
}
pub fn simd_capabilities() -> String {
#[cfg(target_arch = "x86_64")]
{
let mut caps: Vec<&str> = Vec::new();
if is_x86_feature_detected!("avx2") {
caps.push("AVX2");
}
if is_x86_feature_detected!("sse4.2") {
caps.push("SSE4.2");
}
if is_x86_feature_detected!("sse4.1") {
caps.push("SSE4.1");
}
if is_x86_feature_detected!("ssse3") {
caps.push("SSSE3");
}
if is_x86_feature_detected!("sse3") {
caps.push("SSE3");
}
if is_x86_feature_detected!("sse2") {
caps.push("SSE2");
}
if is_x86_feature_detected!("sse") {
caps.push("SSE");
}
if caps.is_empty() {
"None".to_string()
} else {
caps.join(", ")
}
}
#[cfg(not(target_arch = "x86_64"))]
{
"None".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simd_sum_f64() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let expected = 36.0;
let result = simd_sum_f64(&data);
assert!((result - expected).abs() < 1e-10);
}
#[test]
fn test_simd_mean_f64() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let expected = 3.0;
let result = simd_mean_f64(&data);
assert!((result - expected).abs() < 1e-10);
}
#[test]
fn test_simd_min_max_f64() {
let data = vec![3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0];
let min_result = simd_min_f64(&data);
let max_result = simd_max_f64(&data);
assert_eq!(min_result, 1.0);
assert_eq!(max_result, 9.0);
}
#[test]
fn test_simd_sum_i64() {
let data = vec![1i64, 2, 3, 4, 5, 6, 7, 8];
let expected = 36i64;
let result = simd_sum_i64(&data);
assert_eq!(result, expected);
}
#[test]
fn test_simd_capabilities() {
let caps = simd_capabilities();
println!("SIMD capabilities: {}", caps);
assert!(!caps.is_empty());
}
#[test]
fn test_issue_7_simd_capabilities_compiles_on_non_x86_64() {
let caps = simd_capabilities();
assert!(
!caps.is_empty(),
"simd_capabilities() must never return empty"
);
#[cfg(not(target_arch = "x86_64"))]
{
assert_eq!(
caps, "None",
"on non-x86_64 platforms simd_capabilities() must report \"None\""
);
}
}
#[test]
fn test_empty_arrays() {
let empty_f64: Vec<f64> = vec![];
let empty_i64: Vec<i64> = vec![];
assert_eq!(simd_sum_f64(&empty_f64), 0.0);
assert_eq!(simd_mean_f64(&empty_f64), 0.0);
assert_eq!(simd_min_f64(&empty_f64), f64::INFINITY);
assert_eq!(simd_max_f64(&empty_f64), f64::NEG_INFINITY);
assert_eq!(simd_sum_i64(&empty_i64), 0);
assert_eq!(simd_mean_i64(&empty_i64), 0);
assert_eq!(simd_min_i64(&empty_i64), i64::MAX);
assert_eq!(simd_max_i64(&empty_i64), i64::MIN);
}
#[test]
fn simd_sum_f64_bit_identical_to_scalar_reference() {
let fixtures: Vec<Vec<f64>> = vec![
vec![],
vec![1.0],
vec![1.0, 2.0, 3.0],
vec![1.0, 2.0, 3.0, 4.0],
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
vec![f64::NAN, 1.0, 2.0, 3.0, 4.0],
vec![1.0, f64::NAN, 2.0, f64::NAN, 3.0, 4.0, 5.0, f64::NAN, 6.0],
vec![-0.0, 0.0, -0.0, 0.0],
vec![f64::INFINITY, 1.0, f64::NEG_INFINITY, 2.0],
vec![1e308, 1e308, -1e308, 5.0],
];
for data in &fixtures {
let simd = simd_sum_f64(data);
let scalar = sum_skipnan_scalar(data);
assert_eq!(
simd.to_bits(),
scalar.to_bits(),
"simd_sum_f64 != scalar reference for {:?}",
data
);
}
}
#[test]
fn simd_sum_i64_matches_scalar_fold() {
let fixtures: Vec<Vec<i64>> = vec![
vec![],
vec![7],
vec![1, 2, 3],
vec![1, 2, 3, 4, 5, 6, 7, 8, 9],
vec![-5, -4, -3, -2, -1, 0, 1, 2, 3],
vec![i64::MAX, 1, -1, i64::MIN, 0, 0, 0, 0],
];
for data in &fixtures {
assert_eq!(
simd_sum_i64(data),
sum_i64_scalar(data),
"simd_sum_i64 != scalar fold for {:?}",
data
);
}
}
#[test]
fn min_max_skip_nan_keep_inf() {
let data = vec![1.0, f64::NAN, -2.0, f64::INFINITY, 3.0];
assert_eq!(simd_min_f64(&data), -2.0);
assert_eq!(simd_max_f64(&data), f64::INFINITY);
let all_nan = vec![f64::NAN, f64::NAN, f64::NAN, f64::NAN, f64::NAN];
assert_eq!(simd_min_f64(&all_nan), f64::INFINITY);
assert_eq!(simd_max_f64(&all_nan), f64::NEG_INFINITY);
}
}