diff --git a/src/swqos/serialization.rs b/src/swqos/serialization.rs index 796a6b4..59b25a7 100644 --- a/src/swqos/serialization.rs +++ b/src/swqos/serialization.rs @@ -233,6 +233,7 @@ pub fn get_serializer_stats() -> (usize, usize) { #[cfg(test)] mod tests { use super::*; + use std::time::Instant; #[test] fn test_base64_encode() { @@ -274,4 +275,85 @@ mod tests { let (available_after, _) = serializer.get_pool_stats(); assert_eq!(available_after, 1); } + + fn legacy_eager_zero_fill_serializer( + pool_size: usize, + buffer_size: usize, + ) -> ZeroAllocSerializer { + let pool = ArrayQueue::new(pool_size); + for _ in 0..pool_size { + let mut buffer = Vec::with_capacity(buffer_size); + buffer.resize(buffer_size, 0); + let _ = pool.push(buffer); + } + ZeroAllocSerializer { buffer_pool: Arc::new(pool), buffer_size } + } + + /// Manual perf test: compares old eager cold-start behavior to current bounded prewarm. + /// Run with: + /// cargo test --release perf_serializer_cold_start_vs_legacy_eager -- --ignored --nocapture + #[test] + #[ignore = "manual perf benchmark"] + fn perf_serializer_cold_start_vs_legacy_eager() { + const POOL_SIZE: usize = 4096; + const BUFFER_SIZE: usize = 32 * 1024; + const PREWARM: usize = 64; + let payload = vec![7u8; 4096]; + + let legacy_init_start = Instant::now(); + let legacy = legacy_eager_zero_fill_serializer(POOL_SIZE, BUFFER_SIZE); + let legacy_init = legacy_init_start.elapsed(); + + let current_init_start = Instant::now(); + let current = ZeroAllocSerializer::new_with_prewarm(POOL_SIZE, BUFFER_SIZE, PREWARM); + let current_init = current_init_start.elapsed(); + + let legacy_first_start = Instant::now(); + let legacy_buf = legacy.serialize_zero_alloc(&payload, "perf").unwrap(); + let legacy_first = legacy_first_start.elapsed(); + legacy.return_buffer(legacy_buf); + + let current_first_start = Instant::now(); + let current_buf = current.serialize_zero_alloc(&payload, "perf").unwrap(); + let current_first = current_first_start.elapsed(); + current.return_buffer(current_buf); + + println!( + "[perf] serializer cold-start compare\n pool_size={POOL_SIZE} buffer_size={BUFFER_SIZE} prewarm={PREWARM}\n legacy_init={legacy_init:?} current_init={current_init:?}\n legacy_first_serialize={legacy_first:?} current_first_serialize={current_first:?}" + ); + + assert!( + current_init <= legacy_init, + "expected bounded prewarm init ({current_init:?}) to be <= legacy eager init ({legacy_init:?})" + ); + } + + /// Manual perf test: demonstrates lazy allocation amortization. + /// Run with: + /// cargo test --release perf_serializer_lazy_growth_amortization -- --ignored --nocapture + #[test] + #[ignore = "manual perf benchmark"] + fn perf_serializer_lazy_growth_amortization() { + const POOL_SIZE: usize = 128; + const BUFFER_SIZE: usize = 256 * 1024; + let serializer = ZeroAllocSerializer::new_with_prewarm(POOL_SIZE, BUFFER_SIZE, 0); + let payload = vec![1u8; 8 * 1024]; + + let first_start = Instant::now(); + let first_buf = serializer.serialize_zero_alloc(&payload, "perf").unwrap(); + let first = first_start.elapsed(); + serializer.return_buffer(first_buf); + + let second_start = Instant::now(); + let second_buf = serializer.serialize_zero_alloc(&payload, "perf").unwrap(); + let second = second_start.elapsed(); + serializer.return_buffer(second_buf); + + let (available, capacity) = serializer.get_pool_stats(); + println!( + "[perf] serializer lazy growth\n pool_size={POOL_SIZE} buffer_size={BUFFER_SIZE}\n first_serialize={first:?} second_serialize={second:?}\n available={available} capacity={capacity}" + ); + assert!(available >= 1); + assert_eq!(capacity, POOL_SIZE); + } }