use picoring::ring::PicoRing;
use std::hint::black_box;
use std::time::Instant;
fn format_size(bytes: usize) -> String {
if bytes < 1024 {
format!("{} B", bytes)
} else if bytes < 1024 * 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
} else {
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
}
}
fn print_row(label: &str, pico_ns: f64, classic_ns: f64) {
let multiplier = classic_ns / pico_ns;
let format_time = |ns: f64| {
if ns < 1.0 {
format!("{:.0} ps", ns * 1000.0)
} else if ns < 1000.0 {
format!("{:.1} ns", ns)
} else if ns < 1_000_000.0 {
format!("{:.2} µs", ns / 1000.0)
} else {
format!("{:.2} ms", ns / 1_000_000.0)
}
};
println!(
"{:<15} | {:>15} | {:>15} | {:>10.2}x",
label,
format_time(pico_ns),
format_time(classic_ns),
multiplier
);
}
#[test]
fn ultimate_performance_showdown() {
let capacity_bytes = 600 * 1024 * 1024;
println!(
"\nInitializing PicoRing and Classic buffers ({})...",
format_size(capacity_bytes)
);
let mut ring =
PicoRing::<u8>::with_capacity(capacity_bytes).expect("Failed to create PicoRing");
let mut classic_vec = vec![0u8; capacity_bytes];
let test_sizes = [
8, 64, 4096, 65536, 1048576, 10 * 1024 * 1024, 50 * 1024 * 1024, 100 * 1024 * 1024, 250 * 1024 * 1024, 500 * 1024 * 1024, ];
println!("\n[1/3] WRITE PERFORMANCE (Cross-Boundary Copy)");
println!(
"Description: Copying data into the ring buffer when it crosses the physical boundary."
);
println!("{:-<75}", "");
println!(
"{:<15} | {:>15} | {:>15} | {:>11}",
"Packet Size", "Pico (avg)", "Classic (avg)", "Speedup"
);
println!("{:-<75}", "");
for &size in &test_sizes {
let iterations = if size < 1_000_000 {
10_000
} else if size < 100_000_000 {
100
} else {
20
};
let data_to_push = vec![42u8; size];
let split_pos = capacity_bytes - (size / 2);
let start_pico = Instant::now();
for _ in 0..iterations {
unsafe {
let dest = ring.as_mut_slice().as_mut_ptr().add(split_pos);
core::ptr::copy_nonoverlapping(data_to_push.as_ptr(), dest, size);
black_box(core::ptr::read_volatile(dest));
}
}
let pico_avg = start_pico.elapsed().as_nanos() as f64 / iterations as f64;
let start_classic = Instant::now();
for _ in 0..iterations {
let split_at = size / 2;
let (first, second) = data_to_push.split_at(split_at);
classic_vec[split_pos..capacity_bytes].copy_from_slice(first);
classic_vec[0..(size - split_at)].copy_from_slice(second);
black_box(classic_vec[0]);
}
let classic_avg = start_classic.elapsed().as_nanos() as f64 / iterations as f64;
print_row(&format_size(size), pico_avg, classic_avg);
}
println!("\n[2/3] READ PERFORMANCE (Zero-Copy vs Reassemble)");
println!("Description: Accessing a contiguous slice of data that wraps around the buffer.");
println!("{:-<75}", "");
println!(
"{:<15} | {:>15} | {:>15} | {:>11}",
"Read Size", "Pico (avg)", "Classic (avg)", "Speedup"
);
println!("{:-<75}", "");
for &size in &test_sizes {
let iterations = if size < 1_000_000 {
10_000
} else if size < 100_000_000 {
500
} else {
100
};
let split_pos = capacity_bytes - (size / 2);
let start_pico = Instant::now();
for _ in 0..iterations {
let slice = &ring.as_mut_slice()[split_pos..(split_pos + size)];
black_box(slice[0]);
black_box(slice[size - 1]);
}
let pico_avg = start_pico.elapsed().as_nanos() as f64 / iterations as f64;
let start_classic = Instant::now();
for _ in 0..iterations {
let mut temp_buf = vec![0u8; size]; let first_part_len = capacity_bytes - split_pos;
temp_buf[0..first_part_len].copy_from_slice(&classic_vec[split_pos..capacity_bytes]);
temp_buf[first_part_len..size]
.copy_from_slice(&classic_vec[0..(size - first_part_len)]);
black_box(temp_buf[0]);
black_box(temp_buf[size - 1]);
}
let classic_avg = start_classic.elapsed().as_nanos() as f64 / iterations as f64;
print_row(&format_size(size), pico_avg, classic_avg);
}
println!("\n[3/3] FULL CYCLE PERFORMANCE (Write + Read)");
println!("Description: Combined time to write a packet and read it back immediately.");
println!("{:-<75}", "");
println!(
"{:<15} | {:>15} | {:>15} | {:>11}",
"Cycle Size", "Pico (avg)", "Classic (avg)", "Speedup"
);
println!("{:-<75}", "");
for &size in &test_sizes {
let iterations = if size < 1_000_000 {
5_000
} else if size < 100_000_000 {
50
} else {
10
};
let data_to_write = vec![77u8; size];
let split_pos = capacity_bytes - (size / 2);
let start_pico = Instant::now();
for _ in 0..iterations {
if let Some(buf) = ring.view_mut(split_pos, size) {
buf.copy_from_slice(&data_to_write);
}
if let Some(slice) = ring.view(split_pos, size) {
black_box(slice[size / 2]);
}
}
let pico_avg = start_pico.elapsed().as_nanos() as f64 / iterations as f64;
let start_classic = Instant::now();
for _ in 0..iterations {
let split = size / 2;
let (f, s) = data_to_write.split_at(split);
classic_vec[split_pos..capacity_bytes].copy_from_slice(f);
classic_vec[0..(size - split)].copy_from_slice(s);
let mut temp = vec![0u8; size];
let flen = capacity_bytes - split_pos;
temp[0..flen].copy_from_slice(&classic_vec[split_pos..capacity_bytes]);
temp[flen..size].copy_from_slice(&classic_vec[0..(size - flen)]);
black_box(temp[size / 2]);
}
let classic_avg = start_classic.elapsed().as_nanos() as f64 / iterations as f64;
print_row(&format_size(size), pico_avg, classic_avg);
}
}