use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimdAvailability {
Avx2,
Avx,
Sse2,
Neon,
None,
}
impl SimdAvailability {
pub fn is_available(&self) -> bool {
*self != SimdAvailability::None
}
}
static DETECTED: OnceLock<SimdAvailability> = OnceLock::new();
pub fn detect() -> SimdAvailability {
*DETECTED.get_or_init(|| {
let avail = detect_impl();
if !avail.is_available() {
tracing::warn!("SIMD fallback to scalar");
}
avail
})
}
#[cfg(target_arch = "x86_64")]
fn detect_impl() -> SimdAvailability {
if is_x86_feature_detected!("avx2") {
SimdAvailability::Avx2
} else if is_x86_feature_detected!("avx") {
SimdAvailability::Avx
} else if is_x86_feature_detected!("sse2") {
SimdAvailability::Sse2
} else {
SimdAvailability::None
}
}
#[cfg(target_arch = "x86")]
fn detect_impl() -> SimdAvailability {
if is_x86_feature_detected!("avx2") {
SimdAvailability::Avx2
} else if is_x86_feature_detected!("avx") {
SimdAvailability::Avx
} else if is_x86_feature_detected!("sse2") {
SimdAvailability::Sse2
} else {
SimdAvailability::None
}
}
#[cfg(target_arch = "aarch64")]
fn detect_impl() -> SimdAvailability {
if std::arch::is_aarch64_feature_detected!("neon") {
SimdAvailability::Neon
} else {
SimdAvailability::None
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
fn detect_impl() -> SimdAvailability {
SimdAvailability::None
}
pub const SIMD_THRESHOLD: usize = 1024;
pub fn batch_decode_integers(buf: &[u8], count: usize, _avail: SimdAvailability) -> Vec<i64> {
scalar_decode_integers(buf, count)
}
pub fn scalar_decode_integers(buf: &[u8], count: usize) -> Vec<i64> {
let n = count.min(buf.len() / 8);
(0..n)
.map(|i| {
let offset = i * 8;
i64::from_le_bytes(buf[offset..offset + 8].try_into().unwrap())
})
.collect()
}
#[allow(clippy::chunks_exact_to_as_chunks)]
pub fn batch_compare_eq(values: &[i64], target: i64, _avail: SimdAvailability) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for chunk in values.chunks_exact(8) {
result.push(chunk[0] == target);
result.push(chunk[1] == target);
result.push(chunk[2] == target);
result.push(chunk[3] == target);
result.push(chunk[4] == target);
result.push(chunk[5] == target);
result.push(chunk[6] == target);
result.push(chunk[7] == target);
}
for &v in values.chunks_exact(8).remainder() {
result.push(v == target);
}
result
}
pub fn scalar_compare_eq(values: &[i64], target: i64) -> Vec<bool> {
values.iter().map(|&v| v == target).collect()
}
pub fn batch_compare_in(values: &[i64], set: &[i64], _avail: SimdAvailability) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
if set.len() >= 8 {
let hash_set: std::collections::HashSet<i64> = set.iter().copied().collect();
for &v in values {
result.push(hash_set.contains(&v));
}
} else if set.len() >= 3 {
let mut sorted_set: Vec<i64> = set.to_vec();
sorted_set.sort_unstable();
for &v in values {
result.push(sorted_set.binary_search(&v).is_ok());
}
} else {
for &v in values {
result.push(set.contains(&v));
}
}
result
}
pub fn scalar_compare_in(values: &[i64], set: &[i64]) -> Vec<bool> {
values.iter().map(|&v| set.contains(&v)).collect()
}
pub fn batch_sum_f32(data: &[f32]) -> f32 {
data.iter().copied().sum()
}
pub fn batch_count_nonzero(data: &[f64]) -> usize {
data.iter().filter(|&&v| v != 0.0).count()
}
pub fn batch_min_f32(data: &[f32]) -> Option<f32> {
data.iter().copied().fold(None, |acc, v| match acc {
None => Some(v),
Some(m) => Some(m.min(v)),
})
}
pub fn batch_max_f32(data: &[f32]) -> Option<f32> {
data.iter().copied().fold(None, |acc, v| match acc {
None => Some(v),
Some(m) => Some(m.max(v)),
})
}
pub fn batch_cosine_distance(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() || a.is_empty() {
return 0.0;
}
let dot: f32 = a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum();
let norm_a: f32 = a.iter().map(|&x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|&x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot / (norm_a * norm_b)
}
pub fn batch_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() {
return 0.0;
}
a.iter()
.zip(b.iter())
.map(|(&x, &y)| {
let diff = x - y;
diff * diff
})
.sum::<f32>()
.sqrt()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimdCmpOp {
Eq,
Lt,
Le,
Gt,
Ge,
Ne,
}
impl SimdCmpOp {
#[inline]
fn apply_f64(self, a: f64, b: f64) -> bool {
match self {
SimdCmpOp::Eq => a == b,
SimdCmpOp::Lt => a < b,
SimdCmpOp::Le => a <= b,
SimdCmpOp::Gt => a > b,
SimdCmpOp::Ge => a >= b,
SimdCmpOp::Ne => a != b,
}
}
#[inline]
fn apply_f32(self, a: f32, b: f32) -> bool {
match self {
SimdCmpOp::Eq => a == b,
SimdCmpOp::Lt => a < b,
SimdCmpOp::Le => a <= b,
SimdCmpOp::Gt => a > b,
SimdCmpOp::Ge => a >= b,
SimdCmpOp::Ne => a != b,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimdAggOp {
Sum,
Min,
Max,
Avg,
}
pub fn batch_filter_f32(data: &[f32], threshold: f32, op: SimdCmpOp) -> Vec<bool> {
scalar_filter_f32(data, threshold, op)
}
pub fn scalar_filter_f32(data: &[f32], threshold: f32, op: SimdCmpOp) -> Vec<bool> {
data.iter().map(|&v| op.apply_f32(v, threshold)).collect()
}
pub fn batch_filter_f64(data: &[f64], threshold: f64, op: SimdCmpOp) -> Vec<bool> {
scalar_filter_f64(data, threshold, op)
}
pub fn scalar_filter_f64(data: &[f64], threshold: f64, op: SimdCmpOp) -> Vec<bool> {
data.iter().map(|&v| op.apply_f64(v, threshold)).collect()
}
pub fn batch_filter_bool(data: &[bool], expected: bool) -> Vec<bool> {
data.iter().map(|&v| v == expected).collect()
}
pub fn batch_aggregate_f32(data: &[f32], op: SimdAggOp) -> f64 {
if data.is_empty() {
return match op {
SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
};
}
match op {
SimdAggOp::Sum => data.iter().map(|&v| v as f64).sum(),
SimdAggOp::Min => data.iter().map(|&v| v as f64).fold(f64::INFINITY, f64::min),
SimdAggOp::Max => data
.iter()
.map(|&v| v as f64)
.fold(f64::NEG_INFINITY, f64::max),
SimdAggOp::Avg => {
let sum: f64 = data.iter().map(|&v| v as f64).sum();
sum / data.len() as f64
}
}
}
pub fn batch_aggregate_f64(data: &[f64], op: SimdAggOp) -> f64 {
if data.is_empty() {
return match op {
SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
};
}
match op {
SimdAggOp::Sum => data.iter().sum(),
SimdAggOp::Min => data.iter().copied().fold(f64::INFINITY, f64::min),
SimdAggOp::Max => data.iter().copied().fold(f64::NEG_INFINITY, f64::max),
SimdAggOp::Avg => {
let sum: f64 = data.iter().sum();
sum / data.len() as f64
}
}
}
const CACHE_LINE_SIZE: usize = 64;
const F32_BLOCK_SIZE: usize = 64;
const F64_BLOCK_SIZE: usize = 64;
pub fn batch_aggregate_enhanced_f32(data: &[f32], op: SimdAggOp) -> f64 {
if data.is_empty() {
return match op {
SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
};
}
let result = match op {
SimdAggOp::Sum => enhanced_sum_f32(data),
SimdAggOp::Min => enhanced_min_f32(data),
SimdAggOp::Max => enhanced_max_f32(data),
SimdAggOp::Avg => {
let sum = enhanced_sum_f32(data);
sum / data.len() as f64
}
};
verify_aggregate_consistency(op, result, batch_aggregate_f32(data, op))
}
pub fn batch_aggregate_enhanced_f64(data: &[f64], op: SimdAggOp) -> f64 {
if data.is_empty() {
return match op {
SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
};
}
let result = match op {
SimdAggOp::Sum => enhanced_sum_f64(data),
SimdAggOp::Min => enhanced_min_f64(data),
SimdAggOp::Max => enhanced_max_f64(data),
SimdAggOp::Avg => {
let sum = enhanced_sum_f64(data);
sum / data.len() as f64
}
};
verify_aggregate_consistency(op, result, batch_aggregate_f64(data, op))
}
#[inline]
fn verify_aggregate_consistency(op: SimdAggOp, enhanced: f64, scalar: f64) -> f64 {
if enhanced.is_nan() && scalar.is_nan() {
return scalar;
}
if enhanced.is_infinite() && scalar.is_infinite() && enhanced.signum() == scalar.signum() {
return scalar;
}
let diff = (enhanced - scalar).abs();
let tolerance = 1e-6 * scalar.abs().max(1.0);
if diff > tolerance {
tracing::warn!(
target: "sz_orm_core::simd",
code = "ACCELERATION_RESULT_MISMATCH",
?op,
enhanced,
scalar,
"SIMD 增强聚合结果与标量不一致,回退标量路径"
);
return scalar;
}
enhanced
}
#[inline]
fn enhanced_sum_f32(data: &[f32]) -> f64 {
let mut sum0 = 0.0f64;
let mut sum1 = 0.0f64;
let mut sum2 = 0.0f64;
let mut sum3 = 0.0f64;
for chunk in data.chunks_exact(F32_BLOCK_SIZE) {
for i in 0..16 {
sum0 += chunk[i] as f64;
sum1 += chunk[i + 16] as f64;
sum2 += chunk[i + 32] as f64;
sum3 += chunk[i + 48] as f64;
}
}
for &v in data.chunks_exact(F32_BLOCK_SIZE).remainder() {
sum0 += v as f64;
}
(sum0 + sum1) + (sum2 + sum3)
}
#[inline]
fn enhanced_min_f32(data: &[f32]) -> f64 {
let mut min0 = f64::INFINITY;
let mut min1 = f64::INFINITY;
let mut min2 = f64::INFINITY;
let mut min3 = f64::INFINITY;
for chunk in data.chunks_exact(F32_BLOCK_SIZE) {
for i in 0..16 {
min0 = min0.min(chunk[i] as f64);
min1 = min1.min(chunk[i + 16] as f64);
min2 = min2.min(chunk[i + 32] as f64);
min3 = min3.min(chunk[i + 48] as f64);
}
}
let mut result = min0.min(min1).min(min2).min(min3);
for &v in data.chunks_exact(F32_BLOCK_SIZE).remainder() {
result = result.min(v as f64);
}
result
}
#[inline]
fn enhanced_max_f32(data: &[f32]) -> f64 {
let mut max0 = f64::NEG_INFINITY;
let mut max1 = f64::NEG_INFINITY;
let mut max2 = f64::NEG_INFINITY;
let mut max3 = f64::NEG_INFINITY;
for chunk in data.chunks_exact(F32_BLOCK_SIZE) {
for i in 0..16 {
max0 = max0.max(chunk[i] as f64);
max1 = max1.max(chunk[i + 16] as f64);
max2 = max2.max(chunk[i + 32] as f64);
max3 = max3.max(chunk[i + 48] as f64);
}
}
let mut result = max0.max(max1).max(max2).max(max3);
for &v in data.chunks_exact(F32_BLOCK_SIZE).remainder() {
result = result.max(v as f64);
}
result
}
#[inline]
fn enhanced_sum_f64(data: &[f64]) -> f64 {
let mut sum0 = 0.0f64;
let mut sum1 = 0.0f64;
let mut sum2 = 0.0f64;
let mut sum3 = 0.0f64;
for chunk in data.chunks_exact(F64_BLOCK_SIZE) {
for i in 0..16 {
sum0 += chunk[i];
sum1 += chunk[i + 16];
sum2 += chunk[i + 32];
sum3 += chunk[i + 48];
}
}
for &v in data.chunks_exact(F64_BLOCK_SIZE).remainder() {
sum0 += v;
}
(sum0 + sum1) + (sum2 + sum3)
}
#[inline]
fn enhanced_min_f64(data: &[f64]) -> f64 {
let mut min0 = f64::INFINITY;
let mut min1 = f64::INFINITY;
let mut min2 = f64::INFINITY;
let mut min3 = f64::INFINITY;
for chunk in data.chunks_exact(F64_BLOCK_SIZE) {
for i in 0..16 {
min0 = min0.min(chunk[i]);
min1 = min1.min(chunk[i + 16]);
min2 = min2.min(chunk[i + 32]);
min3 = min3.min(chunk[i + 48]);
}
}
let mut result = min0.min(min1).min(min2).min(min3);
for &v in data.chunks_exact(F64_BLOCK_SIZE).remainder() {
result = result.min(v);
}
result
}
#[inline]
fn enhanced_max_f64(data: &[f64]) -> f64 {
let mut max0 = f64::NEG_INFINITY;
let mut max1 = f64::NEG_INFINITY;
let mut max2 = f64::NEG_INFINITY;
let mut max3 = f64::NEG_INFINITY;
for chunk in data.chunks_exact(F64_BLOCK_SIZE) {
for i in 0..16 {
max0 = max0.max(chunk[i]);
max1 = max1.max(chunk[i + 16]);
max2 = max2.max(chunk[i + 32]);
max3 = max3.max(chunk[i + 48]);
}
}
let mut result = max0.max(max1).max(max2).max(max3);
for &v in data.chunks_exact(F64_BLOCK_SIZE).remainder() {
result = result.max(v);
}
result
}
pub fn batch_filter_bitmap_f32(data: &[f32], threshold: f32, op: SimdCmpOp) -> Vec<u64> {
let bitmap_len = data.len().div_ceil(64);
let mut bitmap = Vec::with_capacity(bitmap_len);
for chunk in data.chunks_exact(64) {
let mut bits = 0u64;
for i in 0..64 {
if op.apply_f32(chunk[i], threshold) {
bits |= 1u64 << i;
}
}
bitmap.push(bits);
}
let remainder = data.chunks_exact(64).remainder();
if !remainder.is_empty() {
let mut bits = 0u64;
for (i, &v) in remainder.iter().enumerate() {
if op.apply_f32(v, threshold) {
bits |= 1u64 << i;
}
}
bitmap.push(bits);
}
bitmap
}
pub fn batch_filter_bitmap_f64(data: &[f64], threshold: f64, op: SimdCmpOp) -> Vec<u64> {
let bitmap_len = data.len().div_ceil(64);
let mut bitmap = Vec::with_capacity(bitmap_len);
for chunk in data.chunks_exact(64) {
let mut bits = 0u64;
for i in 0..64 {
if op.apply_f64(chunk[i], threshold) {
bits |= 1u64 << i;
}
}
bitmap.push(bits);
}
let remainder = data.chunks_exact(64).remainder();
if !remainder.is_empty() {
let mut bits = 0u64;
for (i, &v) in remainder.iter().enumerate() {
if op.apply_f64(v, threshold) {
bits |= 1u64 << i;
}
}
bitmap.push(bits);
}
bitmap
}
pub fn bitmap_to_indices(bitmap: &[u64]) -> Vec<usize> {
let mut indices = Vec::new();
for (block_idx, &bits) in bitmap.iter().enumerate() {
let mut bits = bits;
while bits != 0 {
let trailing = bits.trailing_zeros() as usize;
indices.push(block_idx * 64 + trailing);
bits &= bits - 1;
}
}
indices
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simd_availability_is_available() {
assert!(SimdAvailability::Avx2.is_available());
assert!(SimdAvailability::Avx.is_available());
assert!(SimdAvailability::Sse2.is_available());
assert!(SimdAvailability::Neon.is_available());
assert!(!SimdAvailability::None.is_available());
}
#[test]
fn test_detect_returns_cached() {
let d1 = detect();
let d2 = detect();
assert_eq!(d1, d2);
}
#[test]
fn test_simd_fallback_log_on_none() {
let avail = SimdAvailability::None;
assert!(!avail.is_available());
}
#[test]
fn test_simd_detect_does_not_panic() {
let _ = detect();
}
#[test]
fn test_scalar_decode_integers() {
let values: Vec<i64> = vec![1, 2, 3, 4, 5];
let mut buf = Vec::new();
for v in &values {
buf.extend_from_slice(&v.to_le_bytes());
}
let result = scalar_decode_integers(&buf, 5);
assert_eq!(result, values);
}
#[test]
fn test_batch_decode_integers_small_count() {
let values: Vec<i64> = vec![1, 2, 3];
let mut buf = Vec::new();
for v in &values {
buf.extend_from_slice(&v.to_le_bytes());
}
let result = batch_decode_integers(&buf, 3, SimdAvailability::Avx2);
assert_eq!(result, values);
}
#[test]
fn test_batch_decode_integers_large_count() {
let n: usize = 2000;
let values: Vec<i64> = (0..n as i64).map(|i| i * 2 - 1).collect();
let mut buf = Vec::new();
for v in &values {
buf.extend_from_slice(&v.to_le_bytes());
}
let avail = detect();
let result = batch_decode_integers(&buf, n, avail);
assert_eq!(result, values);
}
#[test]
fn test_batch_decode_integers_none_avail() {
let n: usize = 2000;
let values: Vec<i64> = (0..n as i64).collect();
let mut buf = Vec::new();
for v in &values {
buf.extend_from_slice(&v.to_le_bytes());
}
let result = batch_decode_integers(&buf, n, SimdAvailability::None);
assert_eq!(result, values);
}
#[test]
fn test_scalar_compare_eq() {
let values = vec![1, 2, 3, 4, 5, 3, 3];
let result = scalar_compare_eq(&values, 3);
assert_eq!(result, vec![false, false, true, false, false, true, true]);
}
#[test]
fn test_batch_compare_eq_small() {
let values = vec![1, 2, 3, 4, 5];
let result = batch_compare_eq(&values, 3, SimdAvailability::Avx2);
assert_eq!(result, vec![false, false, true, false, false]);
}
#[test]
fn test_batch_compare_eq_large() {
let n: usize = 2000;
let values: Vec<i64> = (0..n as i64).collect();
let target = 500_i64;
let avail = detect();
let result = batch_compare_eq(&values, target, avail);
assert_eq!(result.len(), n);
assert!(result[500]);
assert!(!result[499]);
assert!(!result[501]);
}
#[test]
fn test_scalar_compare_in() {
let values = vec![1, 2, 3, 4, 5];
let set = vec![2, 4];
let result = scalar_compare_in(&values, &set);
assert_eq!(result, vec![false, true, false, true, false]);
}
#[test]
fn test_batch_compare_in_small() {
let values = vec![1, 2, 3, 4, 5];
let set = vec![2, 4];
let result = batch_compare_in(&values, &set, SimdAvailability::Avx2);
assert_eq!(result, vec![false, true, false, true, false]);
}
#[test]
fn test_batch_compare_in_large() {
let n: usize = 2000;
let values: Vec<i64> = (0..n as i64).collect();
let set: Vec<i64> = vec![100, 500, 1500];
let avail = detect();
let result = batch_compare_in(&values, &set, avail);
assert_eq!(result.len(), n);
assert!(result[100]);
assert!(result[500]);
assert!(result[1500]);
assert!(!result[200]);
}
#[test]
fn test_batch_compare_eq_none_avail() {
let n: usize = 2000;
let values: Vec<i64> = (0..n as i64).collect();
let result = batch_compare_eq(&values, 500, SimdAvailability::None);
assert_eq!(result.len(), n);
assert!(result[500]);
}
#[test]
fn test_batch_compare_in_empty_set() {
let values = vec![1, 2, 3];
let set: Vec<i64> = vec![];
let result = batch_compare_in(&values, &set, SimdAvailability::Avx2);
assert_eq!(result, vec![false, false, false]);
}
#[test]
fn test_batch_decode_integers_count_exceeds_buf() {
let values: Vec<i64> = vec![1, 2, 3];
let mut buf = Vec::new();
for v in &values {
buf.extend_from_slice(&v.to_le_bytes());
}
let result = batch_decode_integers(&buf, 100, SimdAvailability::None);
assert_eq!(result, values);
}
#[test]
fn test_batch_decode_integers_empty() {
let result = batch_decode_integers(&[], 0, SimdAvailability::Avx2);
assert!(result.is_empty());
}
#[test]
fn test_simd_threshold_constant() {
assert_eq!(SIMD_THRESHOLD, 1024);
}
#[test]
fn test_batch_compare_eq_boundary_1023() {
let n = 1023;
let values: Vec<i64> = vec![42; n];
let result = batch_compare_eq(&values, 42, SimdAvailability::Avx2);
assert!(result.iter().all(|&b| b));
}
#[test]
fn test_batch_compare_eq_boundary_1024() {
let n = 1024;
let values: Vec<i64> = vec![42; n];
let avail = detect();
let result = batch_compare_eq(&values, 42, avail);
assert!(result.iter().all(|&b| b));
}
#[test]
fn test_batch_compare_eq_boundary_1025() {
let n = 1025;
let values: Vec<i64> = vec![42; n];
let avail = detect();
let result = batch_compare_eq(&values, 42, avail);
assert!(result.iter().all(|&b| b));
}
#[test]
fn test_batch_aggregate_enhanced_f32_sum() {
let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum);
let scalar = batch_aggregate_f32(&data, SimdAggOp::Sum);
assert!((enhanced - scalar).abs() < 1e-3, "enhanced={} scalar={}", enhanced, scalar);
}
#[test]
fn test_batch_aggregate_enhanced_f32_min() {
let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Min);
assert_eq!(enhanced, 0.0);
}
#[test]
fn test_batch_aggregate_enhanced_f32_max() {
let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Max);
assert_eq!(enhanced, 9999.0);
}
#[test]
fn test_batch_aggregate_enhanced_f32_avg() {
let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Avg);
let scalar = batch_aggregate_f32(&data, SimdAggOp::Avg);
assert!((enhanced - scalar).abs() < 1e-3);
}
#[test]
fn test_batch_aggregate_enhanced_f32_empty() {
let data: Vec<f32> = vec![];
assert_eq!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum), 0.0);
assert!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Min).is_nan());
assert!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Max).is_nan());
assert_eq!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Avg), 0.0);
}
#[test]
fn test_batch_aggregate_enhanced_f32_remainder() {
let data: Vec<f32> = (0..70).map(|i| i as f32).collect();
let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum);
let scalar = batch_aggregate_f32(&data, SimdAggOp::Sum);
assert!((enhanced - scalar).abs() < 1e-3);
}
#[test]
fn test_batch_aggregate_enhanced_f64_sum() {
let data: Vec<f64> = (0..10000).map(|i| i as f64).collect();
let enhanced = batch_aggregate_enhanced_f64(&data, SimdAggOp::Sum);
let scalar = batch_aggregate_f64(&data, SimdAggOp::Sum);
assert!((enhanced - scalar).abs() < 1e-6);
}
#[test]
fn test_batch_aggregate_enhanced_f64_min_max() {
let data: Vec<f64> = (0..10000).map(|i| i as f64 * 2.5 - 100.0).collect();
assert_eq!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Min), -100.0);
assert_eq!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Max), 24897.5);
}
#[test]
fn test_batch_aggregate_enhanced_f64_empty() {
let data: Vec<f64> = vec![];
assert_eq!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Sum), 0.0);
assert!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Min).is_nan());
}
#[test]
fn test_batch_aggregate_enhanced_f64_avg() {
let data: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let enhanced = batch_aggregate_enhanced_f64(&data, SimdAggOp::Avg);
assert!((enhanced - 3.0).abs() < 1e-10);
}
#[test]
fn test_batch_aggregate_enhanced_consistency_negative() {
let data: Vec<f32> = vec![-1.5, 2.5, -3.5, 4.5, -5.5];
let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum);
let scalar = batch_aggregate_f32(&data, SimdAggOp::Sum);
assert!((enhanced - scalar).abs() < 1e-6);
}
#[test]
fn test_batch_filter_bitmap_f32_basic() {
let data: Vec<f32> = (0..128).map(|i| i as f32).collect();
let bitmap = batch_filter_bitmap_f32(&data, 50.0, SimdCmpOp::Gt);
let indices = bitmap_to_indices(&bitmap);
assert_eq!(indices, (51..128).collect::<Vec<_>>());
}
#[test]
fn test_batch_filter_bitmap_f32_eq() {
let data: Vec<f32> = vec![1.0, 2.0, 3.0, 2.0, 1.0, 2.0];
let bitmap = batch_filter_bitmap_f32(&data, 2.0, SimdCmpOp::Eq);
let indices = bitmap_to_indices(&bitmap);
assert_eq!(indices, vec![1, 3, 5]);
}
#[test]
fn test_batch_filter_bitmap_f32_empty() {
let data: Vec<f32> = vec![];
let bitmap = batch_filter_bitmap_f32(&data, 0.0, SimdCmpOp::Gt);
assert!(bitmap.is_empty());
}
#[test]
fn test_batch_filter_bitmap_f32_remainder() {
let data: Vec<f32> = (0..70).map(|i| i as f32).collect();
let bitmap = batch_filter_bitmap_f32(&data, 60.0, SimdCmpOp::Ge);
let indices = bitmap_to_indices(&bitmap);
assert_eq!(indices, (60..70).collect::<Vec<_>>());
}
#[test]
fn test_batch_filter_bitmap_f64_basic() {
let data: Vec<f64> = (0..128).map(|i| i as f64).collect();
let bitmap = batch_filter_bitmap_f64(&data, 50.0, SimdCmpOp::Lt);
let indices = bitmap_to_indices(&bitmap);
assert_eq!(indices, (0..50).collect::<Vec<_>>());
}
#[test]
fn test_batch_filter_bitmap_f64_ne() {
let data: Vec<f64> = vec![1.0, 2.0, 3.0, 2.0, 1.0];
let bitmap = batch_filter_bitmap_f64(&data, 2.0, SimdCmpOp::Ne);
let indices = bitmap_to_indices(&bitmap);
assert_eq!(indices, vec![0, 2, 4]);
}
#[test]
fn test_batch_filter_bitmap_memory_efficiency() {
let n = 10000;
let data: Vec<f32> = (0..n).map(|i| i as f32).collect();
let bitmap = batch_filter_bitmap_f32(&data, 0.0, SimdCmpOp::Gt);
let bool_vec = batch_filter_f32(&data, 0.0, SimdCmpOp::Gt);
let bitmap_bytes = bitmap.len() * 8;
let bool_bytes = bool_vec.len();
assert!(bitmap_bytes < bool_bytes, "bitmap={}B bool={}B", bitmap_bytes, bool_bytes);
}
#[test]
fn test_bitmap_to_indices_all_set() {
let bitmap = vec![u64::MAX];
let indices = bitmap_to_indices(&bitmap);
assert_eq!(indices.len(), 64);
assert_eq!(indices[0], 0);
assert_eq!(indices[63], 63);
}
#[test]
fn test_bitmap_to_indices_empty() {
let bitmap = vec![0u64];
let indices = bitmap_to_indices(&bitmap);
assert!(indices.is_empty());
}
#[test]
fn test_batch_filter_bitmap_consistency_with_scalar() {
let data: Vec<f32> = (0..200).map(|i| i as f32 * 0.5).collect();
for op in [SimdCmpOp::Eq, SimdCmpOp::Ne, SimdCmpOp::Lt, SimdCmpOp::Le, SimdCmpOp::Gt, SimdCmpOp::Ge] {
let bitmap = batch_filter_bitmap_f32(&data, 50.0, op);
let bitmap_indices = bitmap_to_indices(&bitmap);
let scalar_result = scalar_filter_f32(&data, 50.0, op);
let scalar_indices: Vec<usize> = scalar_result.iter().enumerate().filter(|(_, &b)| b).map(|(i, _)| i).collect();
assert_eq!(bitmap_indices, scalar_indices, "op={:?}", op);
}
}
#[test]
fn test_cache_line_size_constant() {
assert_eq!(CACHE_LINE_SIZE, 64);
assert_eq!(F32_BLOCK_SIZE, 64);
assert_eq!(F64_BLOCK_SIZE, 64);
}
}