//! Small Batch Performance Benchmark //! //! Validates the optimizations for small batch order processing //! Target: 10K+ orders/sec (sub-100μs latency) for 1-10 order batches use common::{FinancialPrice, FinancialQuantity, OrderSide as Side, OrderType}; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use std::time::{Duration, Instant}; use trading_engine::lockfree::{ BatchMode, LockFreeRingBuffer, SmallBatchOrdersSoA, SmallBatchRing, }; use trading_engine::small_batch_optimizer::{OrderRequest, SmallBatchProcessor}; fn test_qty(val: f64) -> FinancialQuantity { FinancialQuantity::from_f64(val).unwrap_or(FinancialQuantity::ZERO) } fn test_price(val: f64) -> FinancialPrice { FinancialPrice::from_f64(val).unwrap_or(FinancialPrice::ZERO) } /// Benchmark small batch processor vs standard processing fn benchmark_small_batch_vs_standard(c: &mut Criterion) { let mut group = c.benchmark_group("small_batch_vs_standard"); group.measurement_time(Duration::from_secs(10)); for batch_size in &[1, 2, 4, 8, 10] { // Small batch optimized processor group.bench_with_input( BenchmarkId::new("optimized_processor", batch_size), batch_size, |b, &batch_size| { let mut processor = SmallBatchProcessor::new(); b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for i in 0..iters { let start = Instant::now(); // Add orders to batch for j in 0..batch_size { let order = OrderRequest::new( i * batch_size + j, "BTCUSD", if j % 2 == 0 { Side::Buy } else { Side::Sell }, OrderType::Limit, test_qty(1000.0 + j as f64), test_price(50000.0 + (j as f64 * 0.01)), ); processor.add_order(order).expect("Failed to add order"); } // Process batch let _result = processor.process_batch().expect("Failed to process batch"); let end = Instant::now(); total_duration += end.duration_since(start); black_box(&_result); } total_duration }); }, ); // Standard processing (simulated) group.bench_with_input( BenchmarkId::new("standard_processing", batch_size), batch_size, |b, &batch_size| { b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for i in 0..iters { let start = Instant::now(); // Simulate standard order processing overhead for j in 0..batch_size { // Simulate heap allocation let order_data = vec![i * batch_size + j, 50000 + j, 1000 + j]; // Simulate validation let _valid = order_data[1] > 0 && order_data[2] > 0; // Simulate atomic operations (expensive) let counter = std::sync::atomic::AtomicU64::new(0); counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst); let _count = counter.load(std::sync::atomic::Ordering::SeqCst); black_box(&order_data); } let end = Instant::now(); total_duration += end.duration_since(start); } total_duration }); }, ); } group.finish(); } /// Benchmark lock-free ring buffer optimizations fn benchmark_lockfree_optimizations(c: &mut Criterion) { let mut group = c.benchmark_group("lockfree_optimizations"); group.measurement_time(Duration::from_secs(5)); // Standard lock-free ring buffer group.bench_function("standard_lockfree", |b| { let buffer = LockFreeRingBuffer::::new(1024).expect("Failed to create buffer"); b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for i in 0..iters { let start = Instant::now(); // Push small batch (1-8 items) one at a time for j in 0..8 { let _ = buffer.try_push(i * 8 + j); } // Pop small batch one at a time for _ in 0..8 { let _ = buffer.try_pop(); } let end = Instant::now(); total_duration += end.duration_since(start); } total_duration }); }); // Optimized small batch ring buffer group.bench_function("optimized_small_batch", |b| { let buffer = SmallBatchRing::::new(1024, BatchMode::SingleThreaded) .expect("Failed to create buffer"); b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for i in 0..iters { let start = Instant::now(); // Push small batch as single operation let items: Vec = (0..8).map(|j| i * 8 + j).collect(); let _ = buffer.push_batch(&items); // Pop small batch as single operation let mut output = [0u64; 8]; let _ = buffer.pop_batch(&mut output); let end = Instant::now(); total_duration += end.duration_since(start); black_box(&output); } total_duration }); }); group.finish(); } /// Benchmark SIMD optimizations for small batches fn benchmark_simd_optimizations(c: &mut Criterion) { let mut group = c.benchmark_group("simd_optimizations"); group.measurement_time(Duration::from_secs(5)); // Test structure-of-arrays layout group.bench_function("structure_of_arrays", |b| { b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for _i in 0..iters { let start = Instant::now(); let mut soa = SmallBatchOrdersSoA::new(); // Add orders in SoA layout for j in 0..8 { soa.add_order( j as u64, 0x123456 + j as u64, j % 2, 1, 1000.0 + j as f64, 50000.0 + j as f64, 1000 + j as u64, ); } // Calculate total notional using SIMD #[cfg(target_arch = "x86_64")] let _total_notional = soa.calculate_total_notional_simd(); #[cfg(not(target_arch = "x86_64"))] let _total_notional = soa.calculate_total_notional_scalar(); let end = Instant::now(); total_duration += end.duration_since(start); black_box(&soa); } total_duration }); }); // Test array-of-structures layout (standard) group.bench_function("array_of_structures", |b| { // Use canonical Order types from common module #[derive(Clone, Copy)] #[allow(dead_code)] struct BenchOrder { order_id: u64, symbol_hash: u64, side: u8, order_type: u8, quantity: f64, price: f64, timestamp: u64, } b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for _i in 0..iters { let start = Instant::now(); let mut orders = Vec::with_capacity(8); // Add orders in AoS layout for j in 0..8 { orders.push(BenchOrder { order_id: j as u64, symbol_hash: 0x123456 + j as u64, side: j % 2, order_type: 1, quantity: 1000.0 + j as f64, price: 50000.0 + j as f64, timestamp: 1000 + j as u64, }); } // Calculate total notional (scalar) let _total_notional: f64 = orders.iter().map(|o| o.price * o.quantity).sum(); let end = Instant::now(); total_duration += end.duration_since(start); black_box(&orders); } total_duration }); }); group.finish(); } /// Benchmark memory allocation patterns fn benchmark_memory_allocation(c: &mut Criterion) { let mut group = c.benchmark_group("memory_allocation"); group.measurement_time(Duration::from_secs(5)); // Stack allocation group.bench_function("stack_allocation", |b| { b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for i in 0..iters { let start = Instant::now(); // Stack allocated array let mut orders = [0u64; 10]; for (j, slot) in orders.iter_mut().enumerate() { *slot = i * 10 + j as u64; } // Process stack array let _sum: u64 = orders.iter().sum(); let end = Instant::now(); total_duration += end.duration_since(start); black_box(&orders); } total_duration }); }); // Heap allocation group.bench_function("heap_allocation", |b| { b.iter_custom(|iters| { let mut total_duration = Duration::from_nanos(0); for i in 0..iters { let start = Instant::now(); // Heap allocated vector let mut orders = Vec::with_capacity(10); for j in 0..10 { orders.push(i * 10 + j); } // Process heap vector let _sum: u64 = orders.iter().sum(); let end = Instant::now(); total_duration += end.duration_since(start); black_box(&orders); } total_duration }); }); group.finish(); } /// Comprehensive latency validation for small batches fn benchmark_latency_validation(c: &mut Criterion) { let mut group = c.benchmark_group("latency_validation"); group.measurement_time(Duration::from_secs(15)); group.bench_function("target_validation", |b| { let mut processor = SmallBatchProcessor::new(); b.iter_custom(|iters| { let mut latencies = Vec::with_capacity(iters as usize); let mut under_100us = 0u64; let mut under_50us = 0u64; for i in 0..iters { let start = Instant::now(); // Create small batch (5 orders) for j in 0..5 { let order = OrderRequest::new( i * 5 + j, "EURUSD", if j % 2 == 0 { Side::Buy } else { Side::Sell }, OrderType::Limit, test_qty(1000.0 + j as f64), test_price(1.1000 + (j as f64 * 0.0001)), ); processor.add_order(order).expect("Failed to add order"); } // Process batch let _result = processor.process_batch().expect("Failed to process batch"); let latency = start.elapsed(); let latency_us = latency.as_micros() as u64; latencies.push(latency_us); if latency_us <= 100 { under_100us += 1; } if latency_us <= 50 { under_50us += 1; } black_box(&_result); } // Calculate statistics if !latencies.is_empty() { latencies.sort_unstable(); let avg_latency = latencies.iter().sum::() / latencies.len() as u64; let p50_latency = latencies[latencies.len() / 2]; let p95_latency = latencies[(latencies.len() * 95) / 100]; let p99_latency = latencies[(latencies.len() * 99) / 100]; let percent_under_100us = (under_100us as f64 / iters as f64) * 100.0; let percent_under_50us = (under_50us as f64 / iters as f64) * 100.0; eprintln!("\nSmall Batch Latency Results:"); eprintln!(" Average latency: {}μs", avg_latency); eprintln!(" P50 latency: {}μs", p50_latency); eprintln!(" P95 latency: {}μs", p95_latency); eprintln!(" P99 latency: {}μs", p99_latency); eprintln!(" {:.1}% under 100μs target", percent_under_100us); eprintln!(" {:.1}% under 50μs stretch target", percent_under_50us); // Calculate throughput let throughput = if avg_latency > 0 { 1_000_000.0 / avg_latency as f64 // Convert μs to ops/sec } else { 0.0 }; eprintln!(" Throughput: {:.0} orders/sec", throughput); if throughput >= 10000.0 { eprintln!(" ✅ TARGET ACHIEVED: 10K+ orders/sec"); } else { eprintln!(" ❌ TARGET MISSED: {:.0} < 10K orders/sec", throughput); } } // Return total processing time for Criterion Duration::from_micros(latencies.iter().sum::()) }); }); group.finish(); } criterion_group!( small_batch_benches, benchmark_small_batch_vs_standard, benchmark_lockfree_optimizations, benchmark_simd_optimizations, benchmark_memory_allocation, benchmark_latency_validation ); criterion_main!(small_batch_benches);