benchmark.rs (3233B)
1 use std::alloc::{GlobalAlloc, Layout, System}; 2 use std::fmt::Debug; 3 use std::sync::Once; 4 use std::sync::atomic::{AtomicUsize, Ordering}; 5 use std::time::{Duration, Instant}; 6 use tracing::info; 7 8 static ALLOCATED: AtomicUsize = AtomicUsize::new(0); 9 static TRACING_INIT: Once = Once::new(); 10 11 struct TrackingAllocator; 12 13 unsafe impl GlobalAlloc for TrackingAllocator { 14 unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 15 ALLOCATED.fetch_add(layout.size(), Ordering::SeqCst); 16 unsafe { System.alloc(layout) } 17 } 18 19 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 20 ALLOCATED.fetch_sub(layout.size(), Ordering::SeqCst); 21 unsafe { System.dealloc(ptr, layout) } 22 } 23 } 24 25 #[global_allocator] 26 static TRACKING_ALLOCATOR: TrackingAllocator = TrackingAllocator; 27 28 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 29 pub struct BenchmarkResult { 30 pub elapsed: Duration, 31 pub heap_delta_bytes: isize, 32 } 33 34 pub fn current_heap_allocated_bytes() -> usize { 35 ALLOCATED.load(Ordering::SeqCst) 36 } 37 38 pub fn init_benchmark_tracing() { 39 TRACING_INIT.call_once(|| { 40 let _ = tracing_subscriber::fmt() 41 .with_test_writer() 42 .with_target(false) 43 .with_level(false) 44 .without_time() 45 .compact() 46 .try_init(); 47 }); 48 } 49 50 fn format_duration(elapsed: Duration) -> String { 51 let nanos = elapsed.as_nanos(); 52 if nanos >= 1_000_000_000 { 53 format!("{:.3} s", nanos as f64 / 1_000_000_000.0) 54 } else if nanos >= 1_000_000 { 55 format!("{:.3} ms", nanos as f64 / 1_000_000.0) 56 } else if nanos >= 1_000 { 57 format!("{:.3} us", nanos as f64 / 1_000.0) 58 } else { 59 format!("{nanos} ns") 60 } 61 } 62 63 fn format_signed_bytes(bytes: isize) -> String { 64 let sign = if bytes >= 0 { "+" } else { "-" }; 65 let abs = (bytes as i128).abs() as f64; 66 if abs >= 1024.0 * 1024.0 { 67 format!("{sign}{:.2} MiB", abs / (1024.0 * 1024.0)) 68 } else if abs >= 1024.0 { 69 format!("{sign}{:.2} KiB", abs / 1024.0) 70 } else { 71 format!("{sign}{} B", abs as u64) 72 } 73 } 74 75 pub fn run_with_metrics<T, F>(label: &str, test: F) -> (T, BenchmarkResult) 76 where 77 F: FnOnce() -> T, 78 { 79 init_benchmark_tracing(); 80 81 let mem_before = current_heap_allocated_bytes() as isize; 82 let start = Instant::now(); 83 84 let output = test(); 85 86 let elapsed = start.elapsed(); 87 let mem_after = current_heap_allocated_bytes() as isize; 88 let result = BenchmarkResult { 89 elapsed, 90 heap_delta_bytes: mem_after - mem_before, 91 }; 92 93 info!( 94 "\n[benchmark] {label}\n time : {}\n heap delta : {}", 95 format_duration(result.elapsed), 96 format_signed_bytes(result.heap_delta_bytes) 97 ); 98 99 (output, result) 100 } 101 102 pub fn assert_vec_any_order<T>(mut actual: Vec<T>, mut expected: Vec<T>) 103 where 104 T: Ord + Debug, 105 { 106 actual.sort_unstable(); 107 expected.sort_unstable(); 108 assert_eq!(actual, expected); 109 } 110 111 pub fn run_and_assert_vec_any_order<T, F>(label: &str, expected: Vec<T>, test: F) -> BenchmarkResult 112 where 113 T: Ord + Debug, 114 F: FnOnce() -> Vec<T>, 115 { 116 let (actual, metrics) = run_with_metrics(label, test); 117 assert_vec_any_order(actual, expected); 118 metrics 119 }