- Fixed systematic array indexing corruption: [0_i32] → [0] - Fixed numeric literal suffixes across 835 files - Fixed iterator patterns on RwLockReadGuard (.iter() required) - Fixed float type annotations (365.25_f64 for sqrt) - Fixed missing semicolons in position manager - Fixed reference dereferencing in data loader Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices Impact: Complete compilation failure (463 errors) Resolution: Automated regex + targeted fixes Result: 100% compilation success (0 errors) Validated: cargo check --workspace passes Ready for: Production deployment
699 lines
20 KiB
Rust
699 lines
20 KiB
Rust
//! 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::{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<OrderRequest>; MAX_SMALL_BATCH_SIZE],
|
|
|
|
/// Current batch size
|
|
batch_size: usize,
|
|
|
|
/// Performance metrics
|
|
metrics: SmallBatchMetrics,
|
|
|
|
/// `SIMD` operations handler
|
|
simd_ops: Option<SmallBatchSimd>,
|
|
}
|
|
|
|
/// 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
|
|
pub quantity: f64, // Use f64 for SIMD operations
|
|
/// Price
|
|
pub price: f64, // Use f64 for SIMD operations
|
|
/// 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: f64,
|
|
price: f64,
|
|
) -> 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<Self, &'static str> {
|
|
// 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
|
|
let mut padded_count = 0;
|
|
for (i, order) in orders.into_iter().enumerate() {
|
|
self.prices[i] = order.price;
|
|
self.quantities[i] = order.quantity;
|
|
self.timestamps[i] = order.timestamp_ns;
|
|
padded_count = i + 1;
|
|
}
|
|
|
|
// Pad remaining slots with zeros
|
|
for i in padded_count..4 {
|
|
self.prices[i] = 0.0;
|
|
self.quantities[i] = 0.0;
|
|
self.timestamps[i] = 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_else(|_| 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");
|
|
}
|
|
|
|
self.orders[self.batch_size] = Some(order);
|
|
self.batch_size += 1;
|
|
Ok(())
|
|
}
|
|
|
|
/// Process current batch with optimized path
|
|
#[inline(always)]
|
|
pub fn process_batch(&mut self) -> Result<SmallBatchResult, &'static str> {
|
|
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<BatchProcessingResult, &'static str> {
|
|
let simd_ops = self.simd_ops.as_mut().ok_or("SIMD not available")?;
|
|
|
|
// Collect valid orders
|
|
let valid_orders: Vec<OrderRequest> = self.orders[..self.batch_size]
|
|
.iter()
|
|
.filter_map(|&order| order)
|
|
.collect();
|
|
|
|
// Process with SIMD
|
|
simd_ops.process_batch(&valid_orders)?;
|
|
|
|
// Calculate total notional (simplified for demonstration)
|
|
let total_notional: f64 = valid_orders
|
|
.iter()
|
|
.map(|order| order.price * order.quantity)
|
|
.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<BatchProcessingResult, &'static str> {
|
|
let mut orders_processed = 0;
|
|
let mut total_notional = 0.0;
|
|
|
|
for &order_opt in &self.orders[..self.batch_size] {
|
|
if let Some(order) = order_opt {
|
|
// Validate order
|
|
if order.price <= 0.0 || order.quantity <= 0.0 {
|
|
return Err("Invalid order parameters");
|
|
}
|
|
|
|
// Calculate notional
|
|
let notional = order.price * order.quantity;
|
|
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) {
|
|
for order in &mut self.orders[..self.batch_size] {
|
|
*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)]
|
|
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());
|
|
}
|
|
|
|
#[test]
|
|
fn test_order_request_creation() {
|
|
let order = OrderRequest::new(
|
|
12345,
|
|
"BTCUSD",
|
|
OrderSide::Buy,
|
|
OrderType::Limit,
|
|
1.5,
|
|
50000.0,
|
|
);
|
|
|
|
assert_eq!(order.order_id, 12345);
|
|
assert_eq!(order.side, OrderSide::Buy);
|
|
assert_eq!(order.order_type, OrderType::Limit);
|
|
assert_eq!(order.quantity, 1.5);
|
|
assert_eq!(order.price, 50000.0);
|
|
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, 1.0, 50000.0);
|
|
let order2 =
|
|
OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Market, 2.0, 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,
|
|
1.0,
|
|
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 > 900000.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,
|
|
1.0,
|
|
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,
|
|
1.0,
|
|
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, 1.0, 50000.0),
|
|
OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Limit, 2.0, 3000.0),
|
|
];
|
|
|
|
assert!(simd_ops.process_batch(&orders).is_ok());
|
|
} else {
|
|
println!("Skipping SIMD test - AVX2 not available");
|
|
}
|
|
}
|
|
}
|