1use std::sync::OnceLock;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SimdAvailability {
19 Avx2,
21 Avx,
23 Sse2,
25 Neon,
27 None,
29}
30
31impl SimdAvailability {
32 pub fn is_available(&self) -> bool {
34 *self != SimdAvailability::None
35 }
36}
37
38static DETECTED: OnceLock<SimdAvailability> = OnceLock::new();
39
40pub fn detect() -> SimdAvailability {
42 *DETECTED.get_or_init(|| {
43 let avail = detect_impl();
44 if !avail.is_available() {
45 tracing::warn!("SIMD fallback to scalar");
46 }
47 avail
48 })
49}
50
51#[cfg(target_arch = "x86_64")]
52fn detect_impl() -> SimdAvailability {
53 if is_x86_feature_detected!("avx2") {
54 SimdAvailability::Avx2
55 } else if is_x86_feature_detected!("avx") {
56 SimdAvailability::Avx
57 } else if is_x86_feature_detected!("sse2") {
58 SimdAvailability::Sse2
59 } else {
60 SimdAvailability::None
61 }
62}
63
64#[cfg(target_arch = "x86")]
65fn detect_impl() -> SimdAvailability {
66 if is_x86_feature_detected!("avx2") {
67 SimdAvailability::Avx2
68 } else if is_x86_feature_detected!("avx") {
69 SimdAvailability::Avx
70 } else if is_x86_feature_detected!("sse2") {
71 SimdAvailability::Sse2
72 } else {
73 SimdAvailability::None
74 }
75}
76
77#[cfg(target_arch = "aarch64")]
78fn detect_impl() -> SimdAvailability {
79 if std::arch::is_aarch64_feature_detected!("neon") {
80 SimdAvailability::Neon
81 } else {
82 SimdAvailability::None
83 }
84}
85
86#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
87fn detect_impl() -> SimdAvailability {
88 SimdAvailability::None
89}
90
91pub const SIMD_THRESHOLD: usize = 1024;
93
94pub fn batch_decode_integers(buf: &[u8], count: usize, _avail: SimdAvailability) -> Vec<i64> {
105 scalar_decode_integers(buf, count)
106}
107
108pub fn scalar_decode_integers(buf: &[u8], count: usize) -> Vec<i64> {
110 let n = count.min(buf.len() / 8);
111 (0..n)
112 .map(|i| {
113 let offset = i * 8;
114 i64::from_le_bytes(buf[offset..offset + 8].try_into().unwrap())
115 })
116 .collect()
117}
118
119#[allow(clippy::chunks_exact_to_as_chunks)]
130pub fn batch_compare_eq(values: &[i64], target: i64, _avail: SimdAvailability) -> Vec<bool> {
131 let mut result = Vec::with_capacity(values.len());
132 for chunk in values.chunks_exact(8) {
133 result.push(chunk[0] == target);
134 result.push(chunk[1] == target);
135 result.push(chunk[2] == target);
136 result.push(chunk[3] == target);
137 result.push(chunk[4] == target);
138 result.push(chunk[5] == target);
139 result.push(chunk[6] == target);
140 result.push(chunk[7] == target);
141 }
142 for &v in values.chunks_exact(8).remainder() {
143 result.push(v == target);
144 }
145 result
146}
147
148pub fn scalar_compare_eq(values: &[i64], target: i64) -> Vec<bool> {
150 values.iter().map(|&v| v == target).collect()
151}
152
153pub fn batch_compare_in(values: &[i64], set: &[i64], _avail: SimdAvailability) -> Vec<bool> {
162 let mut result = Vec::with_capacity(values.len());
163 if set.len() >= 8 {
164 let hash_set: std::collections::HashSet<i64> = set.iter().copied().collect();
165 for &v in values {
166 result.push(hash_set.contains(&v));
167 }
168 } else if set.len() >= 3 {
169 let mut sorted_set: Vec<i64> = set.to_vec();
170 sorted_set.sort_unstable();
171 for &v in values {
172 result.push(sorted_set.binary_search(&v).is_ok());
173 }
174 } else {
175 for &v in values {
176 result.push(set.contains(&v));
177 }
178 }
179 result
180}
181
182pub fn scalar_compare_in(values: &[i64], set: &[i64]) -> Vec<bool> {
184 values.iter().map(|&v| set.contains(&v)).collect()
185}
186
187pub fn batch_sum_f32(data: &[f32]) -> f32 {
193 data.iter().copied().sum()
194}
195
196pub fn batch_count_nonzero(data: &[f64]) -> usize {
198 data.iter().filter(|&&v| v != 0.0).count()
199}
200
201pub fn batch_min_f32(data: &[f32]) -> Option<f32> {
203 data.iter().copied().fold(None, |acc, v| match acc {
204 None => Some(v),
205 Some(m) => Some(m.min(v)),
206 })
207}
208
209pub fn batch_max_f32(data: &[f32]) -> Option<f32> {
211 data.iter().copied().fold(None, |acc, v| match acc {
212 None => Some(v),
213 Some(m) => Some(m.max(v)),
214 })
215}
216
217pub fn batch_cosine_distance(a: &[f32], b: &[f32]) -> f32 {
219 if a.len() != b.len() || a.is_empty() {
220 return 0.0;
221 }
222 let dot: f32 = a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum();
223 let norm_a: f32 = a.iter().map(|&x| x * x).sum::<f32>().sqrt();
224 let norm_b: f32 = b.iter().map(|&x| x * x).sum::<f32>().sqrt();
225 if norm_a == 0.0 || norm_b == 0.0 {
226 return 0.0;
227 }
228 dot / (norm_a * norm_b)
229}
230
231pub fn batch_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
233 if a.len() != b.len() {
234 return 0.0;
235 }
236 a.iter()
237 .zip(b.iter())
238 .map(|(&x, &y)| {
239 let diff = x - y;
240 diff * diff
241 })
242 .sum::<f32>()
243 .sqrt()
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum SimdCmpOp {
253 Eq,
255 Lt,
257 Le,
259 Gt,
261 Ge,
263 Ne,
265}
266
267impl SimdCmpOp {
268 #[inline]
270 fn apply_f64(self, a: f64, b: f64) -> bool {
271 match self {
272 SimdCmpOp::Eq => a == b,
273 SimdCmpOp::Lt => a < b,
274 SimdCmpOp::Le => a <= b,
275 SimdCmpOp::Gt => a > b,
276 SimdCmpOp::Ge => a >= b,
277 SimdCmpOp::Ne => a != b,
278 }
279 }
280
281 #[inline]
283 fn apply_f32(self, a: f32, b: f32) -> bool {
284 match self {
285 SimdCmpOp::Eq => a == b,
286 SimdCmpOp::Lt => a < b,
287 SimdCmpOp::Le => a <= b,
288 SimdCmpOp::Gt => a > b,
289 SimdCmpOp::Ge => a >= b,
290 SimdCmpOp::Ne => a != b,
291 }
292 }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum SimdAggOp {
298 Sum,
300 Min,
302 Max,
304 Avg,
306}
307
308pub fn batch_filter_f32(data: &[f32], threshold: f32, op: SimdCmpOp) -> Vec<bool> {
314 scalar_filter_f32(data, threshold, op)
315}
316
317pub fn scalar_filter_f32(data: &[f32], threshold: f32, op: SimdCmpOp) -> Vec<bool> {
319 data.iter().map(|&v| op.apply_f32(v, threshold)).collect()
320}
321
322pub fn batch_filter_f64(data: &[f64], threshold: f64, op: SimdCmpOp) -> Vec<bool> {
324 scalar_filter_f64(data, threshold, op)
325}
326
327pub fn scalar_filter_f64(data: &[f64], threshold: f64, op: SimdCmpOp) -> Vec<bool> {
329 data.iter().map(|&v| op.apply_f64(v, threshold)).collect()
330}
331
332pub fn batch_filter_bool(data: &[bool], expected: bool) -> Vec<bool> {
336 data.iter().map(|&v| v == expected).collect()
337}
338
339pub fn batch_aggregate_f32(data: &[f32], op: SimdAggOp) -> f64 {
343 if data.is_empty() {
344 return match op {
345 SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
346 SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
347 };
348 }
349 match op {
350 SimdAggOp::Sum => data.iter().map(|&v| v as f64).sum(),
351 SimdAggOp::Min => data.iter().map(|&v| v as f64).fold(f64::INFINITY, f64::min),
352 SimdAggOp::Max => data
353 .iter()
354 .map(|&v| v as f64)
355 .fold(f64::NEG_INFINITY, f64::max),
356 SimdAggOp::Avg => {
357 let sum: f64 = data.iter().map(|&v| v as f64).sum();
358 sum / data.len() as f64
359 }
360 }
361}
362
363pub fn batch_aggregate_f64(data: &[f64], op: SimdAggOp) -> f64 {
365 if data.is_empty() {
366 return match op {
367 SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
368 SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
369 };
370 }
371 match op {
372 SimdAggOp::Sum => data.iter().sum(),
373 SimdAggOp::Min => data.iter().copied().fold(f64::INFINITY, f64::min),
374 SimdAggOp::Max => data.iter().copied().fold(f64::NEG_INFINITY, f64::max),
375 SimdAggOp::Avg => {
376 let sum: f64 = data.iter().sum();
377 sum / data.len() as f64
378 }
379 }
380}
381
382const CACHE_LINE_SIZE: usize = 64;
388
389const F32_BLOCK_SIZE: usize = 64;
391
392const F64_BLOCK_SIZE: usize = 64;
394
395pub fn batch_aggregate_enhanced_f32(data: &[f32], op: SimdAggOp) -> f64 {
401 if data.is_empty() {
402 return match op {
403 SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
404 SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
405 };
406 }
407
408 let result = match op {
409 SimdAggOp::Sum => enhanced_sum_f32(data),
410 SimdAggOp::Min => enhanced_min_f32(data),
411 SimdAggOp::Max => enhanced_max_f32(data),
412 SimdAggOp::Avg => {
413 let sum = enhanced_sum_f32(data);
414 sum / data.len() as f64
415 }
416 };
417
418 verify_aggregate_consistency(op, result, batch_aggregate_f32(data, op))
419}
420
421pub fn batch_aggregate_enhanced_f64(data: &[f64], op: SimdAggOp) -> f64 {
426 if data.is_empty() {
427 return match op {
428 SimdAggOp::Sum | SimdAggOp::Avg => 0.0,
429 SimdAggOp::Min | SimdAggOp::Max => f64::NAN,
430 };
431 }
432
433 let result = match op {
434 SimdAggOp::Sum => enhanced_sum_f64(data),
435 SimdAggOp::Min => enhanced_min_f64(data),
436 SimdAggOp::Max => enhanced_max_f64(data),
437 SimdAggOp::Avg => {
438 let sum = enhanced_sum_f64(data);
439 sum / data.len() as f64
440 }
441 };
442
443 verify_aggregate_consistency(op, result, batch_aggregate_f64(data, op))
444}
445
446#[inline]
448fn verify_aggregate_consistency(op: SimdAggOp, enhanced: f64, scalar: f64) -> f64 {
449 if enhanced.is_nan() && scalar.is_nan() {
450 return scalar;
451 }
452 if enhanced.is_infinite() && scalar.is_infinite() && enhanced.signum() == scalar.signum() {
453 return scalar;
454 }
455 let diff = (enhanced - scalar).abs();
456 let tolerance = 1e-6 * scalar.abs().max(1.0);
457 if diff > tolerance {
458 tracing::warn!(
459 target: "sz_orm_core::simd",
460 code = "ACCELERATION_RESULT_MISMATCH",
461 ?op,
462 enhanced,
463 scalar,
464 "SIMD 增强聚合结果与标量不一致,回退标量路径"
465 );
466 return scalar;
467 }
468 enhanced
469}
470
471#[inline]
473fn enhanced_sum_f32(data: &[f32]) -> f64 {
474 let mut sum0 = 0.0f64;
475 let mut sum1 = 0.0f64;
476 let mut sum2 = 0.0f64;
477 let mut sum3 = 0.0f64;
478
479 for chunk in data.chunks_exact(F32_BLOCK_SIZE) {
480 for i in 0..16 {
481 sum0 += chunk[i] as f64;
482 sum1 += chunk[i + 16] as f64;
483 sum2 += chunk[i + 32] as f64;
484 sum3 += chunk[i + 48] as f64;
485 }
486 }
487
488 for &v in data.chunks_exact(F32_BLOCK_SIZE).remainder() {
489 sum0 += v as f64;
490 }
491
492 (sum0 + sum1) + (sum2 + sum3)
493}
494
495#[inline]
497fn enhanced_min_f32(data: &[f32]) -> f64 {
498 let mut min0 = f64::INFINITY;
499 let mut min1 = f64::INFINITY;
500 let mut min2 = f64::INFINITY;
501 let mut min3 = f64::INFINITY;
502
503 for chunk in data.chunks_exact(F32_BLOCK_SIZE) {
504 for i in 0..16 {
505 min0 = min0.min(chunk[i] as f64);
506 min1 = min1.min(chunk[i + 16] as f64);
507 min2 = min2.min(chunk[i + 32] as f64);
508 min3 = min3.min(chunk[i + 48] as f64);
509 }
510 }
511
512 let mut result = min0.min(min1).min(min2).min(min3);
513 for &v in data.chunks_exact(F32_BLOCK_SIZE).remainder() {
514 result = result.min(v as f64);
515 }
516 result
517}
518
519#[inline]
521fn enhanced_max_f32(data: &[f32]) -> f64 {
522 let mut max0 = f64::NEG_INFINITY;
523 let mut max1 = f64::NEG_INFINITY;
524 let mut max2 = f64::NEG_INFINITY;
525 let mut max3 = f64::NEG_INFINITY;
526
527 for chunk in data.chunks_exact(F32_BLOCK_SIZE) {
528 for i in 0..16 {
529 max0 = max0.max(chunk[i] as f64);
530 max1 = max1.max(chunk[i + 16] as f64);
531 max2 = max2.max(chunk[i + 32] as f64);
532 max3 = max3.max(chunk[i + 48] as f64);
533 }
534 }
535
536 let mut result = max0.max(max1).max(max2).max(max3);
537 for &v in data.chunks_exact(F32_BLOCK_SIZE).remainder() {
538 result = result.max(v as f64);
539 }
540 result
541}
542
543#[inline]
545fn enhanced_sum_f64(data: &[f64]) -> f64 {
546 let mut sum0 = 0.0f64;
547 let mut sum1 = 0.0f64;
548 let mut sum2 = 0.0f64;
549 let mut sum3 = 0.0f64;
550
551 for chunk in data.chunks_exact(F64_BLOCK_SIZE) {
552 for i in 0..16 {
553 sum0 += chunk[i];
554 sum1 += chunk[i + 16];
555 sum2 += chunk[i + 32];
556 sum3 += chunk[i + 48];
557 }
558 }
559
560 for &v in data.chunks_exact(F64_BLOCK_SIZE).remainder() {
561 sum0 += v;
562 }
563
564 (sum0 + sum1) + (sum2 + sum3)
565}
566
567#[inline]
569fn enhanced_min_f64(data: &[f64]) -> f64 {
570 let mut min0 = f64::INFINITY;
571 let mut min1 = f64::INFINITY;
572 let mut min2 = f64::INFINITY;
573 let mut min3 = f64::INFINITY;
574
575 for chunk in data.chunks_exact(F64_BLOCK_SIZE) {
576 for i in 0..16 {
577 min0 = min0.min(chunk[i]);
578 min1 = min1.min(chunk[i + 16]);
579 min2 = min2.min(chunk[i + 32]);
580 min3 = min3.min(chunk[i + 48]);
581 }
582 }
583
584 let mut result = min0.min(min1).min(min2).min(min3);
585 for &v in data.chunks_exact(F64_BLOCK_SIZE).remainder() {
586 result = result.min(v);
587 }
588 result
589}
590
591#[inline]
593fn enhanced_max_f64(data: &[f64]) -> f64 {
594 let mut max0 = f64::NEG_INFINITY;
595 let mut max1 = f64::NEG_INFINITY;
596 let mut max2 = f64::NEG_INFINITY;
597 let mut max3 = f64::NEG_INFINITY;
598
599 for chunk in data.chunks_exact(F64_BLOCK_SIZE) {
600 for i in 0..16 {
601 max0 = max0.max(chunk[i]);
602 max1 = max1.max(chunk[i + 16]);
603 max2 = max2.max(chunk[i + 32]);
604 max3 = max3.max(chunk[i + 48]);
605 }
606 }
607
608 let mut result = max0.max(max1).max(max2).max(max3);
609 for &v in data.chunks_exact(F64_BLOCK_SIZE).remainder() {
610 result = result.max(v);
611 }
612 result
613}
614
615pub fn batch_filter_bitmap_f32(data: &[f32], threshold: f32, op: SimdCmpOp) -> Vec<u64> {
625 let bitmap_len = data.len().div_ceil(64);
626 let mut bitmap = Vec::with_capacity(bitmap_len);
627
628 for chunk in data.chunks_exact(64) {
629 let mut bits = 0u64;
630 for i in 0..64 {
631 if op.apply_f32(chunk[i], threshold) {
632 bits |= 1u64 << i;
633 }
634 }
635 bitmap.push(bits);
636 }
637
638 let remainder = data.chunks_exact(64).remainder();
639 if !remainder.is_empty() {
640 let mut bits = 0u64;
641 for (i, &v) in remainder.iter().enumerate() {
642 if op.apply_f32(v, threshold) {
643 bits |= 1u64 << i;
644 }
645 }
646 bitmap.push(bits);
647 }
648
649 bitmap
650}
651
652pub fn batch_filter_bitmap_f64(data: &[f64], threshold: f64, op: SimdCmpOp) -> Vec<u64> {
657 let bitmap_len = data.len().div_ceil(64);
658 let mut bitmap = Vec::with_capacity(bitmap_len);
659
660 for chunk in data.chunks_exact(64) {
661 let mut bits = 0u64;
662 for i in 0..64 {
663 if op.apply_f64(chunk[i], threshold) {
664 bits |= 1u64 << i;
665 }
666 }
667 bitmap.push(bits);
668 }
669
670 let remainder = data.chunks_exact(64).remainder();
671 if !remainder.is_empty() {
672 let mut bits = 0u64;
673 for (i, &v) in remainder.iter().enumerate() {
674 if op.apply_f64(v, threshold) {
675 bits |= 1u64 << i;
676 }
677 }
678 bitmap.push(bits);
679 }
680
681 bitmap
682}
683
684pub fn bitmap_to_indices(bitmap: &[u64]) -> Vec<usize> {
689 let mut indices = Vec::new();
690 for (block_idx, &bits) in bitmap.iter().enumerate() {
691 let mut bits = bits;
692 while bits != 0 {
693 let trailing = bits.trailing_zeros() as usize;
694 indices.push(block_idx * 64 + trailing);
695 bits &= bits - 1;
696 }
697 }
698 indices
699}
700
701#[cfg(test)]
706mod tests {
707 use super::*;
708
709 #[test]
710 fn test_simd_availability_is_available() {
711 assert!(SimdAvailability::Avx2.is_available());
712 assert!(SimdAvailability::Avx.is_available());
713 assert!(SimdAvailability::Sse2.is_available());
714 assert!(SimdAvailability::Neon.is_available());
715 assert!(!SimdAvailability::None.is_available());
716 }
717
718 #[test]
719 fn test_detect_returns_cached() {
720 let d1 = detect();
721 let d2 = detect();
722 assert_eq!(d1, d2);
723 }
724
725 #[test]
726 fn test_simd_fallback_log_on_none() {
727 let avail = SimdAvailability::None;
728 assert!(!avail.is_available());
729 }
730
731 #[test]
732 fn test_simd_detect_does_not_panic() {
733 let _ = detect();
734 }
735
736 #[test]
737 fn test_scalar_decode_integers() {
738 let values: Vec<i64> = vec![1, 2, 3, 4, 5];
739 let mut buf = Vec::new();
740 for v in &values {
741 buf.extend_from_slice(&v.to_le_bytes());
742 }
743 let result = scalar_decode_integers(&buf, 5);
744 assert_eq!(result, values);
745 }
746
747 #[test]
748 fn test_batch_decode_integers_small_count() {
749 let values: Vec<i64> = vec![1, 2, 3];
750 let mut buf = Vec::new();
751 for v in &values {
752 buf.extend_from_slice(&v.to_le_bytes());
753 }
754 let result = batch_decode_integers(&buf, 3, SimdAvailability::Avx2);
755 assert_eq!(result, values);
756 }
757
758 #[test]
759 fn test_batch_decode_integers_large_count() {
760 let n: usize = 2000;
761 let values: Vec<i64> = (0..n as i64).map(|i| i * 2 - 1).collect();
762 let mut buf = Vec::new();
763 for v in &values {
764 buf.extend_from_slice(&v.to_le_bytes());
765 }
766 let avail = detect();
767 let result = batch_decode_integers(&buf, n, avail);
768 assert_eq!(result, values);
769 }
770
771 #[test]
772 fn test_batch_decode_integers_none_avail() {
773 let n: usize = 2000;
774 let values: Vec<i64> = (0..n as i64).collect();
775 let mut buf = Vec::new();
776 for v in &values {
777 buf.extend_from_slice(&v.to_le_bytes());
778 }
779 let result = batch_decode_integers(&buf, n, SimdAvailability::None);
780 assert_eq!(result, values);
781 }
782
783 #[test]
784 fn test_scalar_compare_eq() {
785 let values = vec![1, 2, 3, 4, 5, 3, 3];
786 let result = scalar_compare_eq(&values, 3);
787 assert_eq!(result, vec![false, false, true, false, false, true, true]);
788 }
789
790 #[test]
791 fn test_batch_compare_eq_small() {
792 let values = vec![1, 2, 3, 4, 5];
793 let result = batch_compare_eq(&values, 3, SimdAvailability::Avx2);
794 assert_eq!(result, vec![false, false, true, false, false]);
795 }
796
797 #[test]
798 fn test_batch_compare_eq_large() {
799 let n: usize = 2000;
800 let values: Vec<i64> = (0..n as i64).collect();
801 let target = 500_i64;
802 let avail = detect();
803 let result = batch_compare_eq(&values, target, avail);
804 assert_eq!(result.len(), n);
805 assert!(result[500]);
806 assert!(!result[499]);
807 assert!(!result[501]);
808 }
809
810 #[test]
811 fn test_scalar_compare_in() {
812 let values = vec![1, 2, 3, 4, 5];
813 let set = vec![2, 4];
814 let result = scalar_compare_in(&values, &set);
815 assert_eq!(result, vec![false, true, false, true, false]);
816 }
817
818 #[test]
819 fn test_batch_compare_in_small() {
820 let values = vec![1, 2, 3, 4, 5];
821 let set = vec![2, 4];
822 let result = batch_compare_in(&values, &set, SimdAvailability::Avx2);
823 assert_eq!(result, vec![false, true, false, true, false]);
824 }
825
826 #[test]
827 fn test_batch_compare_in_large() {
828 let n: usize = 2000;
829 let values: Vec<i64> = (0..n as i64).collect();
830 let set: Vec<i64> = vec![100, 500, 1500];
831 let avail = detect();
832 let result = batch_compare_in(&values, &set, avail);
833 assert_eq!(result.len(), n);
834 assert!(result[100]);
835 assert!(result[500]);
836 assert!(result[1500]);
837 assert!(!result[200]);
838 }
839
840 #[test]
841 fn test_batch_compare_eq_none_avail() {
842 let n: usize = 2000;
843 let values: Vec<i64> = (0..n as i64).collect();
844 let result = batch_compare_eq(&values, 500, SimdAvailability::None);
845 assert_eq!(result.len(), n);
846 assert!(result[500]);
847 }
848
849 #[test]
850 fn test_batch_compare_in_empty_set() {
851 let values = vec![1, 2, 3];
852 let set: Vec<i64> = vec![];
853 let result = batch_compare_in(&values, &set, SimdAvailability::Avx2);
854 assert_eq!(result, vec![false, false, false]);
855 }
856
857 #[test]
858 fn test_batch_decode_integers_count_exceeds_buf() {
859 let values: Vec<i64> = vec![1, 2, 3];
860 let mut buf = Vec::new();
861 for v in &values {
862 buf.extend_from_slice(&v.to_le_bytes());
863 }
864 let result = batch_decode_integers(&buf, 100, SimdAvailability::None);
865 assert_eq!(result, values);
866 }
867
868 #[test]
869 fn test_batch_decode_integers_empty() {
870 let result = batch_decode_integers(&[], 0, SimdAvailability::Avx2);
871 assert!(result.is_empty());
872 }
873
874 #[test]
875 fn test_simd_threshold_constant() {
876 assert_eq!(SIMD_THRESHOLD, 1024);
877 }
878
879 #[test]
880 fn test_batch_compare_eq_boundary_1023() {
881 let n = 1023;
882 let values: Vec<i64> = vec![42; n];
883 let result = batch_compare_eq(&values, 42, SimdAvailability::Avx2);
884 assert!(result.iter().all(|&b| b));
885 }
886
887 #[test]
888 fn test_batch_compare_eq_boundary_1024() {
889 let n = 1024;
890 let values: Vec<i64> = vec![42; n];
891 let avail = detect();
892 let result = batch_compare_eq(&values, 42, avail);
893 assert!(result.iter().all(|&b| b));
894 }
895
896 #[test]
897 fn test_batch_compare_eq_boundary_1025() {
898 let n = 1025;
899 let values: Vec<i64> = vec![42; n];
900 let avail = detect();
901 let result = batch_compare_eq(&values, 42, avail);
902 assert!(result.iter().all(|&b| b));
903 }
904
905 #[test]
910 fn test_batch_aggregate_enhanced_f32_sum() {
911 let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
912 let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum);
913 let scalar = batch_aggregate_f32(&data, SimdAggOp::Sum);
914 assert!((enhanced - scalar).abs() < 1e-3, "enhanced={} scalar={}", enhanced, scalar);
915 }
916
917 #[test]
918 fn test_batch_aggregate_enhanced_f32_min() {
919 let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
920 let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Min);
921 assert_eq!(enhanced, 0.0);
922 }
923
924 #[test]
925 fn test_batch_aggregate_enhanced_f32_max() {
926 let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
927 let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Max);
928 assert_eq!(enhanced, 9999.0);
929 }
930
931 #[test]
932 fn test_batch_aggregate_enhanced_f32_avg() {
933 let data: Vec<f32> = (0..10000).map(|i| i as f32).collect();
934 let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Avg);
935 let scalar = batch_aggregate_f32(&data, SimdAggOp::Avg);
936 assert!((enhanced - scalar).abs() < 1e-3);
937 }
938
939 #[test]
940 fn test_batch_aggregate_enhanced_f32_empty() {
941 let data: Vec<f32> = vec![];
942 assert_eq!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum), 0.0);
943 assert!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Min).is_nan());
944 assert!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Max).is_nan());
945 assert_eq!(batch_aggregate_enhanced_f32(&data, SimdAggOp::Avg), 0.0);
946 }
947
948 #[test]
949 fn test_batch_aggregate_enhanced_f32_remainder() {
950 let data: Vec<f32> = (0..70).map(|i| i as f32).collect();
951 let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum);
952 let scalar = batch_aggregate_f32(&data, SimdAggOp::Sum);
953 assert!((enhanced - scalar).abs() < 1e-3);
954 }
955
956 #[test]
957 fn test_batch_aggregate_enhanced_f64_sum() {
958 let data: Vec<f64> = (0..10000).map(|i| i as f64).collect();
959 let enhanced = batch_aggregate_enhanced_f64(&data, SimdAggOp::Sum);
960 let scalar = batch_aggregate_f64(&data, SimdAggOp::Sum);
961 assert!((enhanced - scalar).abs() < 1e-6);
962 }
963
964 #[test]
965 fn test_batch_aggregate_enhanced_f64_min_max() {
966 let data: Vec<f64> = (0..10000).map(|i| i as f64 * 2.5 - 100.0).collect();
967 assert_eq!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Min), -100.0);
968 assert_eq!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Max), 24897.5);
969 }
970
971 #[test]
972 fn test_batch_aggregate_enhanced_f64_empty() {
973 let data: Vec<f64> = vec![];
974 assert_eq!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Sum), 0.0);
975 assert!(batch_aggregate_enhanced_f64(&data, SimdAggOp::Min).is_nan());
976 }
977
978 #[test]
979 fn test_batch_aggregate_enhanced_f64_avg() {
980 let data: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0];
981 let enhanced = batch_aggregate_enhanced_f64(&data, SimdAggOp::Avg);
982 assert!((enhanced - 3.0).abs() < 1e-10);
983 }
984
985 #[test]
986 fn test_batch_aggregate_enhanced_consistency_negative() {
987 let data: Vec<f32> = vec![-1.5, 2.5, -3.5, 4.5, -5.5];
988 let enhanced = batch_aggregate_enhanced_f32(&data, SimdAggOp::Sum);
989 let scalar = batch_aggregate_f32(&data, SimdAggOp::Sum);
990 assert!((enhanced - scalar).abs() < 1e-6);
991 }
992
993 #[test]
998 fn test_batch_filter_bitmap_f32_basic() {
999 let data: Vec<f32> = (0..128).map(|i| i as f32).collect();
1000 let bitmap = batch_filter_bitmap_f32(&data, 50.0, SimdCmpOp::Gt);
1001 let indices = bitmap_to_indices(&bitmap);
1002 assert_eq!(indices, (51..128).collect::<Vec<_>>());
1003 }
1004
1005 #[test]
1006 fn test_batch_filter_bitmap_f32_eq() {
1007 let data: Vec<f32> = vec![1.0, 2.0, 3.0, 2.0, 1.0, 2.0];
1008 let bitmap = batch_filter_bitmap_f32(&data, 2.0, SimdCmpOp::Eq);
1009 let indices = bitmap_to_indices(&bitmap);
1010 assert_eq!(indices, vec![1, 3, 5]);
1011 }
1012
1013 #[test]
1014 fn test_batch_filter_bitmap_f32_empty() {
1015 let data: Vec<f32> = vec![];
1016 let bitmap = batch_filter_bitmap_f32(&data, 0.0, SimdCmpOp::Gt);
1017 assert!(bitmap.is_empty());
1018 }
1019
1020 #[test]
1021 fn test_batch_filter_bitmap_f32_remainder() {
1022 let data: Vec<f32> = (0..70).map(|i| i as f32).collect();
1023 let bitmap = batch_filter_bitmap_f32(&data, 60.0, SimdCmpOp::Ge);
1024 let indices = bitmap_to_indices(&bitmap);
1025 assert_eq!(indices, (60..70).collect::<Vec<_>>());
1026 }
1027
1028 #[test]
1029 fn test_batch_filter_bitmap_f64_basic() {
1030 let data: Vec<f64> = (0..128).map(|i| i as f64).collect();
1031 let bitmap = batch_filter_bitmap_f64(&data, 50.0, SimdCmpOp::Lt);
1032 let indices = bitmap_to_indices(&bitmap);
1033 assert_eq!(indices, (0..50).collect::<Vec<_>>());
1034 }
1035
1036 #[test]
1037 fn test_batch_filter_bitmap_f64_ne() {
1038 let data: Vec<f64> = vec![1.0, 2.0, 3.0, 2.0, 1.0];
1039 let bitmap = batch_filter_bitmap_f64(&data, 2.0, SimdCmpOp::Ne);
1040 let indices = bitmap_to_indices(&bitmap);
1041 assert_eq!(indices, vec![0, 2, 4]);
1042 }
1043
1044 #[test]
1045 fn test_batch_filter_bitmap_memory_efficiency() {
1046 let n = 10000;
1047 let data: Vec<f32> = (0..n).map(|i| i as f32).collect();
1048 let bitmap = batch_filter_bitmap_f32(&data, 0.0, SimdCmpOp::Gt);
1049 let bool_vec = batch_filter_f32(&data, 0.0, SimdCmpOp::Gt);
1050 let bitmap_bytes = bitmap.len() * 8;
1051 let bool_bytes = bool_vec.len();
1052 assert!(bitmap_bytes < bool_bytes, "bitmap={}B bool={}B", bitmap_bytes, bool_bytes);
1053 }
1054
1055 #[test]
1056 fn test_bitmap_to_indices_all_set() {
1057 let bitmap = vec![u64::MAX];
1058 let indices = bitmap_to_indices(&bitmap);
1059 assert_eq!(indices.len(), 64);
1060 assert_eq!(indices[0], 0);
1061 assert_eq!(indices[63], 63);
1062 }
1063
1064 #[test]
1065 fn test_bitmap_to_indices_empty() {
1066 let bitmap = vec![0u64];
1067 let indices = bitmap_to_indices(&bitmap);
1068 assert!(indices.is_empty());
1069 }
1070
1071 #[test]
1072 fn test_batch_filter_bitmap_consistency_with_scalar() {
1073 let data: Vec<f32> = (0..200).map(|i| i as f32 * 0.5).collect();
1074 for op in [SimdCmpOp::Eq, SimdCmpOp::Ne, SimdCmpOp::Lt, SimdCmpOp::Le, SimdCmpOp::Gt, SimdCmpOp::Ge] {
1075 let bitmap = batch_filter_bitmap_f32(&data, 50.0, op);
1076 let bitmap_indices = bitmap_to_indices(&bitmap);
1077 let scalar_result = scalar_filter_f32(&data, 50.0, op);
1078 let scalar_indices: Vec<usize> = scalar_result.iter().enumerate().filter(|(_, &b)| b).map(|(i, _)| i).collect();
1079 assert_eq!(bitmap_indices, scalar_indices, "op={:?}", op);
1080 }
1081 }
1082
1083 #[test]
1084 fn test_cache_line_size_constant() {
1085 assert_eq!(CACHE_LINE_SIZE, 64);
1086 assert_eq!(F32_BLOCK_SIZE, 64);
1087 assert_eq!(F64_BLOCK_SIZE, 64);
1088 }
1089}