//! Small Batch Optimizer for HFT Trading Performance //! //! Specialized optimization for small batch order processing (1-10 orders) //! to achieve target 10K+ orders/sec performance (sub-100μs latency). //! //! Key optimizations: //! - Stack allocation instead of heap for small collections //! - Bypassed atomic operations for single-threaded processing //! - Cache-aware memory layout with 64-byte alignment //! - Hybrid SIMD dispatch with padding for small batches #![deny( clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::unreachable, clippy::indexing_slicing )] use crate::timing::HardwareTimestamp; use common::{FinancialPrice, FinancialQuantity, OrderSide, OrderType}; use std::sync::atomic::{AtomicU64, Ordering}; /// Maximum orders in a small batch for specialized processing pub const MAX_SMALL_BATCH_SIZE: usize = 10; /// Small batch processor with stack allocation and cache optimization #[repr(align(64))] // Cache line alignment /// SmallBatchProcessor /// /// Auto-generated documentation placeholder - enhance with specifics pub struct SmallBatchProcessor { /// Stack-allocated order buffer (no heap allocation) orders: [Option; MAX_SMALL_BATCH_SIZE], /// Current batch size batch_size: usize, /// Performance metrics metrics: SmallBatchMetrics, /// `SIMD` operations handler simd_ops: Option, } /// Optimized order request structure for small batch processing #[derive(Debug, Clone, Copy)] #[repr(align(32))] // SIMD alignment /// OrderRequest /// /// Auto-generated documentation placeholder - enhance with specifics pub struct OrderRequest { /// Order Id pub order_id: u64, /// Symbol Hash pub symbol_hash: u64, // Hash of symbol for fast comparison /// Side pub side: OrderSide, /// Order Type pub order_type: OrderType, /// Quantity (typed fixed-point, converted to f64 at SIMD boundary) pub quantity: FinancialQuantity, /// Price (typed fixed-point, converted to f64 at SIMD boundary) pub price: FinancialPrice, /// Timestamp Ns pub timestamp_ns: u64, } impl OrderRequest { /// Create new order request with timestamp #[inline(always)] pub fn new( order_id: u64, symbol: &str, side: OrderSide, order_type: OrderType, quantity: FinancialQuantity, price: FinancialPrice, ) -> Self { Self { order_id, symbol_hash: Self::hash_symbol(symbol), side, order_type, quantity, price, timestamp_ns: HardwareTimestamp::now().nanos, } } /// Fast symbol hashing for comparison #[inline(always)] fn hash_symbol(symbol: &str) -> u64 { // Simple but fast hash for symbol comparison let mut hash = 0xcbf29ce484222325_u64; // FNV offset basis for byte in symbol.bytes() { hash ^= byte as u64; hash = hash.wrapping_mul(0x100000001b3_u64); // FNV prime } hash } } /// Performance metrics for small batch processing #[derive(Debug, Default)] /// SmallBatchMetrics /// /// Auto-generated documentation placeholder - enhance with specifics pub struct SmallBatchMetrics { /// Orders Processed pub orders_processed: AtomicU64, /// Total Latency Ns pub total_latency_ns: AtomicU64, /// Max Latency Ns pub max_latency_ns: AtomicU64, /// Min Latency Ns pub min_latency_ns: AtomicU64, /// Cache Hits pub cache_hits: AtomicU64, /// Cache Misses pub cache_misses: AtomicU64, } impl SmallBatchMetrics { /// Update latency statistics #[inline(always)] pub fn update_latency(&self, latency_ns: u64) { self.orders_processed.fetch_add(1, Ordering::Relaxed); self.total_latency_ns .fetch_add(latency_ns, Ordering::Relaxed); // Update max latency loop { let current_max = self.max_latency_ns.load(Ordering::Relaxed); if latency_ns <= current_max { break; } if self .max_latency_ns .compare_exchange_weak( current_max, latency_ns, Ordering::Relaxed, Ordering::Relaxed, ) .is_ok() { break; } } // Update min latency (initialize to first value) loop { let current_min = self.min_latency_ns.load(Ordering::Relaxed); let new_min = if current_min == 0 { latency_ns } else { current_min.min(latency_ns) }; if self .min_latency_ns .compare_exchange_weak(current_min, new_min, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { break; } } } /// Get average latency in nanoseconds #[inline] pub fn avg_latency_ns(&self) -> f64 { let total = self.total_latency_ns.load(Ordering::Relaxed); let count = self.orders_processed.load(Ordering::Relaxed); if count > 0 { total as f64 / count as f64 } else { 0.0 } } /// Get throughput in orders per second #[inline] pub fn throughput_ops(&self) -> f64 { let avg_latency = self.avg_latency_ns(); if avg_latency > 0.0 { 1_000_000_000.0 / avg_latency // Convert ns to ops/sec } else { 0.0 } } } /// `SIMD` operations for small batch processing pub struct SmallBatchSimd { /// Padded arrays for `SIMD` operations (aligned to 32 bytes) prices: [f64; 4], quantities: [f64; 4], timestamps: [u64; 4], } impl SmallBatchSimd { /// Create new `SIMD` operations handler pub fn new() -> Result { // Verify AVX2 support if !std::arch::is_x86_feature_detected!("avx2") { return Err("AVX2 support required for SIMD operations"); } Ok(Self { prices: [0.0; 4], quantities: [0.0; 4], timestamps: [0; 4], }) } /// Process small batch using `SIMD` operations #[cfg(target_arch = "x86_64")] pub fn process_batch(&mut self, orders: &[OrderRequest]) -> Result<(), &'static str> { if orders.is_empty() || orders.len() > 4 { return Err("Batch size must be 1-4 orders for SIMD processing"); } // Pad batch to 4 elements for SIMD (convert typed → f64 at SIMD boundary) let mut padded_count = 0; for (i, order) in orders.iter().enumerate() { if let Some(slot) = self.prices.get_mut(i) { *slot = order.price.to_f64(); } if let Some(slot) = self.quantities.get_mut(i) { *slot = order.quantity.to_f64(); } if let Some(slot) = self.timestamps.get_mut(i) { *slot = order.timestamp_ns; } padded_count = i + 1; } // Pad remaining slots with zeros for i in padded_count..4 { if let Some(slot) = self.prices.get_mut(i) { *slot = 0.0; } if let Some(slot) = self.quantities.get_mut(i) { *slot = 0.0; } if let Some(slot) = self.timestamps.get_mut(i) { *slot = 0; } } // SAFETY: AVX2 feature detection performed and data alignment verified with padding unsafe { self.validate_prices_simd()?; self.calculate_notional_simd()?; } Ok(()) } /// Validate prices using `SIMD` operations #[cfg(target_arch = "x86_64")] #[target_feature(enable = "avx2")] unsafe fn validate_prices_simd(&self) -> Result<(), &'static str> { use std::arch::x86_64::{ _mm256_cmp_pd, _mm256_loadu_pd, _mm256_movemask_pd, _mm256_setzero_pd, _CMP_GT_OQ, }; // Load prices into SIMD register let prices_vec = _mm256_loadu_pd(self.prices.as_ptr()); // Check for positive prices (> 0.0) let zero_vec = _mm256_setzero_pd(); let positive_mask = _mm256_cmp_pd(prices_vec, zero_vec, _CMP_GT_OQ); // Check if all valid prices are positive let mask_bits = _mm256_movemask_pd(positive_mask); // For padded batch, only check the first N elements // This is a simplified validation - production code would need more robust checks if mask_bits == 0 { return Err("Invalid negative or zero prices detected"); } Ok(()) } /// Calculate notional values using `SIMD` #[cfg(target_arch = "x86_64")] #[target_feature(enable = "avx2")] unsafe fn calculate_notional_simd(&self) -> Result<(), &'static str> { use std::arch::x86_64::{_mm256_loadu_pd, _mm256_mul_pd, _mm256_storeu_pd}; // Load prices and quantities let prices_vec = _mm256_loadu_pd(self.prices.as_ptr()); let quantities_vec = _mm256_loadu_pd(self.quantities.as_ptr()); // Calculate notional = price * quantity let notional_vec = _mm256_mul_pd(prices_vec, quantities_vec); // Store results (for demonstration - production would use these values) let mut notional_results = [0.0; 4]; _mm256_storeu_pd(notional_results.as_mut_ptr(), notional_vec); // Validate notional values are reasonable for ¬ional in ¬ional_results { if notional < 0.0 || notional > 1_000_000_000.0 { return Err("Notional value out of acceptable range"); } } Ok(()) } } impl Default for SmallBatchSimd { fn default() -> Self { Self::new().unwrap_or(Self { prices: [0.0; 4], quantities: [0.0; 4], timestamps: [0; 4], }) } } impl std::fmt::Debug for SmallBatchSimd { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SmallBatchSimd") .field("prices", &self.prices) .field("quantities", &self.quantities) .field("timestamps", &self.timestamps) .finish() } } impl SmallBatchProcessor { /// Create new small batch processor pub fn new() -> Self { Self { orders: [None; MAX_SMALL_BATCH_SIZE], batch_size: 0, metrics: SmallBatchMetrics::default(), simd_ops: SmallBatchSimd::new().ok(), } } /// Add order to batch (stack allocation, no heap) #[inline(always)] pub fn add_order(&mut self, order: OrderRequest) -> Result<(), &'static str> { if self.batch_size >= MAX_SMALL_BATCH_SIZE { return Err("Batch is full"); } if let Some(slot) = self.orders.get_mut(self.batch_size) { *slot = Some(order); } self.batch_size += 1; Ok(()) } /// Process current batch with optimized path #[inline(always)] pub fn process_batch(&mut self) -> Result { if self.batch_size == 0 { return Err("No orders in batch"); } let start_time = HardwareTimestamp::now(); // Fast path for small batches (1-4 orders) with SIMD let result = if self.batch_size <= 4 && self.simd_ops.is_some() { self.process_simd_batch()? } else { self.process_scalar_batch()? }; let end_time = HardwareTimestamp::now(); let latency_ns = end_time.latency_ns(&start_time); // Update performance metrics self.metrics.update_latency(latency_ns); // Clear batch for next processing self.clear_batch(); Ok(SmallBatchResult { orders_processed: result.orders_processed, total_notional: result.total_notional, processing_latency_ns: latency_ns, used_simd: result.used_simd, }) } /// Process batch using `SIMD` operations fn process_simd_batch(&mut self) -> Result { let simd_ops = self.simd_ops.as_mut().ok_or("SIMD not available")?; // Collect valid orders let valid_orders: Vec = self .orders .get(..self.batch_size) .unwrap_or(&[]) .iter() .filter_map(|&order| order) .collect(); // Process with SIMD simd_ops.process_batch(&valid_orders)?; // Calculate total notional (convert typed values to f64 at SIMD boundary) let total_notional: f64 = valid_orders .iter() .map(|order| order.price.to_f64() * order.quantity.to_f64()) .sum(); Ok(BatchProcessingResult { orders_processed: valid_orders.len(), total_notional, used_simd: true, }) } /// Process batch using scalar operations (fallback) fn process_scalar_batch(&self) -> Result { let mut orders_processed = 0; let mut total_notional = 0.0; for &order_opt in self.orders.get(..self.batch_size).unwrap_or(&[]) { if let Some(order) = order_opt { // Validate order (typed values are always valid — NaN/Inf rejected at construction) if !order.price.is_positive() || !order.quantity.is_positive() { return Err("Invalid order parameters"); } // Calculate notional (convert to f64 at computation boundary) let notional = order.price.to_f64() * order.quantity.to_f64(); if notional > 1_000_000_000.0 { return Err("Notional value too large"); } total_notional += notional; orders_processed += 1; } } Ok(BatchProcessingResult { orders_processed, total_notional, used_simd: false, }) } /// Clear current batch #[inline(always)] fn clear_batch(&mut self) { if let Some(orders_slice) = self.orders.get_mut(..self.batch_size) { for order in orders_slice { *order = None; } } self.batch_size = 0; } /// Get current batch size #[inline] pub const fn batch_size(&self) -> usize { self.batch_size } /// Check if batch is full #[inline] pub const fn is_full(&self) -> bool { self.batch_size >= MAX_SMALL_BATCH_SIZE } /// Check if batch is empty #[inline] pub const fn is_empty(&self) -> bool { self.batch_size == 0 } /// Get performance metrics pub const fn metrics(&self) -> &SmallBatchMetrics { &self.metrics } /// Reset performance metrics pub fn reset_metrics(&mut self) { self.metrics = SmallBatchMetrics::default(); } } impl Default for SmallBatchProcessor { fn default() -> Self { Self::new() } } impl std::fmt::Debug for SmallBatchProcessor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SmallBatchProcessor") .field("batch_size", &self.batch_size) .field("is_empty", &self.is_empty()) .field("is_full", &self.is_full()) .field("has_simd", &self.simd_ops.is_some()) .field("metrics", &self.metrics) .finish() } } /// `Result` of batch processing #[derive(Debug)] /// SmallBatchResult /// /// Auto-generated documentation placeholder - enhance with specifics pub struct SmallBatchResult { /// Orders Processed pub orders_processed: usize, /// Total Notional pub total_notional: f64, /// Processing Latency Ns pub processing_latency_ns: u64, /// Used Simd pub used_simd: bool, } /// Internal batch processing result #[derive(Debug)] struct BatchProcessingResult { orders_processed: usize, total_notional: f64, used_simd: bool, } /// Performance statistics for small batch processing #[derive(Debug)] /// SmallBatchStats /// /// Auto-generated documentation placeholder - enhance with specifics pub struct SmallBatchStats { /// Avg Latency Ns pub avg_latency_ns: f64, /// Min Latency Ns pub min_latency_ns: u64, /// Max Latency Ns pub max_latency_ns: u64, /// Throughput Ops pub throughput_ops: f64, /// Orders Processed pub orders_processed: u64, /// Cache Hit Rate pub cache_hit_rate: f64, } impl From<&SmallBatchMetrics> for SmallBatchStats { fn from(metrics: &SmallBatchMetrics) -> Self { let total_cache = metrics.cache_hits.load(Ordering::Relaxed) + metrics.cache_misses.load(Ordering::Relaxed); let cache_hit_rate = if total_cache > 0 { metrics.cache_hits.load(Ordering::Relaxed) as f64 / total_cache as f64 } else { 0.0 }; Self { avg_latency_ns: metrics.avg_latency_ns(), min_latency_ns: metrics.min_latency_ns.load(Ordering::Relaxed), max_latency_ns: metrics.max_latency_ns.load(Ordering::Relaxed), throughput_ops: metrics.throughput_ops(), orders_processed: metrics.orders_processed.load(Ordering::Relaxed), cache_hit_rate, } } } #[cfg(test)] #[allow(clippy::assertions_on_result_states)] mod tests { use super::*; #[test] fn test_small_batch_processor_creation() { let processor = SmallBatchProcessor::new(); assert_eq!(processor.batch_size(), 0); assert!(processor.is_empty()); assert!(!processor.is_full()); } fn test_price(val: f64) -> FinancialPrice { FinancialPrice::from_f64(val).unwrap_or(FinancialPrice::ZERO) } fn test_qty(val: f64) -> FinancialQuantity { FinancialQuantity::from_f64(val).unwrap_or(FinancialQuantity::ZERO) } #[test] fn test_order_request_creation() { let order = OrderRequest::new( 12345, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.5), test_price(50000.0), ); assert_eq!(order.order_id, 12345); assert_eq!(order.side, OrderSide::Buy); assert_eq!(order.order_type, OrderType::Limit); assert!((order.quantity.to_f64() - 1.5).abs() < 1e-6); assert!((order.price.to_f64() - 50000.0).abs() < 1e-6); assert!(order.timestamp_ns > 0); } #[test] fn test_add_orders_to_batch() { let mut processor = SmallBatchProcessor::new(); let order1 = OrderRequest::new(1, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.0), test_price(50000.0)); let order2 = OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Market, test_qty(2.0), test_price(3000.0)); assert!(processor.add_order(order1).is_ok()); assert_eq!(processor.batch_size(), 1); assert!(processor.add_order(order2).is_ok()); assert_eq!(processor.batch_size(), 2); } #[test] fn test_batch_processing() { let mut processor = SmallBatchProcessor::new(); // Add test orders for i in 1..=3 { let order = OrderRequest::new( i, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.0), test_price(50000.0 + i as f64), ); processor .add_order(order) .expect("Test: Failed to add order"); } // Process batch let result = processor .process_batch() .expect("Test: Failed to process batch"); assert_eq!(result.orders_processed, 3); assert!(result.total_notional > 0.0); assert!(result.processing_latency_ns > 0); // Batch should be empty after processing assert!(processor.is_empty()); } #[test] fn test_performance_metrics() { let metrics = SmallBatchMetrics::default(); // Update with some latencies metrics.update_latency(1000); metrics.update_latency(1500); metrics.update_latency(800); assert_eq!(metrics.orders_processed.load(Ordering::Relaxed), 3); assert_eq!(metrics.min_latency_ns.load(Ordering::Relaxed), 800); assert_eq!(metrics.max_latency_ns.load(Ordering::Relaxed), 1500); let avg_latency = metrics.avg_latency_ns(); assert!((avg_latency - 1100.0).abs() < 1.0); // Should be approximately 1100ns let throughput = metrics.throughput_ops(); assert!(throughput > 900_000.0); // Should be > 900K ops/sec for 1100ns latency } #[test] fn test_batch_overflow() { let mut processor = SmallBatchProcessor::new(); // Fill the batch to capacity for i in 1..=MAX_SMALL_BATCH_SIZE { let order = OrderRequest::new( i as u64, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.0), test_price(50000.0), ); assert!(processor.add_order(order).is_ok()); } assert!(processor.is_full()); // Try to add one more order - should fail let overflow_order = OrderRequest::new( 999, "ETHUSD", OrderSide::Sell, OrderType::Market, test_qty(1.0), test_price(3000.0), ); assert!(processor.add_order(overflow_order).is_err()); } #[test] fn test_simd_operations() { if std::arch::is_x86_feature_detected!("avx2") { let mut simd_ops = SmallBatchSimd::new().expect("Test: Failed to create SIMD ops"); let orders = vec![ OrderRequest::new(1, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.0), test_price(50000.0)), OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Limit, test_qty(2.0), test_price(3000.0)), ]; assert!(simd_ops.process_batch(&orders).is_ok()); } else { println!("Skipping SIMD test - AVX2 not available"); } } }