use picoring::PicoList;
use std::collections::{BTreeMap, HashMap, LinkedList, VecDeque};
use std::hint::black_box;
use std::time::Instant;
use sysinfo::{get_current_pid, ProcessRefreshKind, ProcessesToUpdate, System};
fn format_time(ns: f64) -> String {
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)
}
}
fn format_bytes(bytes: u64) -> String {
if bytes < 1024 {
format!("{} B", bytes)
} else if bytes < 1024 * 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
} else if bytes < 1024 * 1024 * 1024 {
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
} else {
format!("{:.1} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
}
fn get_rss_bytes(sys: &mut System) -> u64 {
sys.refresh_processes_specifics(
ProcessesToUpdate::All,
true,
ProcessRefreshKind::everything(),
);
sys.process(get_current_pid().expect("failed to get pid"))
.map(|p| p.memory())
.unwrap_or(0)
}
#[test]
fn rust_collections_scale_analysis() {
let mut sys = System::new();
let baseline_init = get_rss_bytes(&mut sys);
let mega_items = 500_000_000;
let expected_raw_mega = mega_items as u64 * 8;
println!(
"\n--- HIGH-CAPACITY COLLECTION BENCHMARK ({} per Collection) ---",
format_bytes(expected_raw_mega)
);
println!("{:-<145}", "");
println!(
"{:<15} | {:>15} | {:>12} | {:>12} | {:>15} | {:>12} | {:>12}",
"Operation", "PicoList", "Vec", "VecDeque", "LinkedList", "BTreeMap", "HashMap"
);
println!("{:-<145}", "");
let access_count = 100_000;
let indices: Vec<usize> = (0..access_count).map(|i| (i * 13) % mega_items).collect();
let (mut pico_res, mut vec_res, mut deque_res, mut list_res, mut btree_res, mut hash_res) = (
[0.0f64; 3],
[0.0f64; 3],
[0.0f64; 3],
[0.0f64; 3],
[0.0f64; 3],
[0.0f64; 3],
);
let (pico_mem, vec_mem, deque_mem, list_mem, btree_mem, hash_mem);
{
let start = Instant::now();
let mut coll = PicoList::<u64, 131072>::new(); for i in 0..mega_items {
coll.push(black_box(i as u64));
}
pico_res[0] = start.elapsed().as_nanos() as f64;
pico_mem = get_rss_bytes(&mut sys).saturating_sub(baseline_init);
let start = Instant::now();
for &idx in &indices {
black_box(coll.get(idx));
}
pico_res[1] = start.elapsed().as_nanos() as f64 / indices.len() as f64;
black_box(coll); }
let baseline = get_rss_bytes(&mut sys);
{
let start = Instant::now();
let mut coll = Vec::new();
for i in 0..mega_items {
coll.push(black_box(i as u64));
}
vec_res[0] = start.elapsed().as_nanos() as f64;
vec_mem = get_rss_bytes(&mut sys).saturating_sub(baseline);
let start = Instant::now();
for &idx in &indices {
black_box(coll.get(idx));
}
vec_res[1] = start.elapsed().as_nanos() as f64 / indices.len() as f64;
black_box(coll);
}
let baseline = get_rss_bytes(&mut sys);
{
let start = Instant::now();
let mut coll = VecDeque::new();
for i in 0..mega_items {
coll.push_back(black_box(i as u64));
}
deque_res[0] = start.elapsed().as_nanos() as f64;
deque_mem = get_rss_bytes(&mut sys).saturating_sub(baseline);
let start = Instant::now();
for &idx in &indices {
black_box(coll.get(idx));
}
deque_res[1] = start.elapsed().as_nanos() as f64 / indices.len() as f64;
black_box(coll);
}
let baseline = get_rss_bytes(&mut sys);
{
let start = Instant::now();
let mut coll = LinkedList::new();
let items = 1_000_000;
for i in 0..items {
coll.push_back(i as u64);
}
list_res[0] = start.elapsed().as_nanos() as f64 * (mega_items / 1_000_000) as f64;
list_mem =
get_rss_bytes(&mut sys).saturating_sub(baseline) * (mega_items / 1_000_000) as u64;
}
let baseline = get_rss_bytes(&mut sys);
{
let start = Instant::now();
let mut coll = BTreeMap::new();
let items = 1_000_000;
for i in 0..items {
coll.insert(i as u64, i as u64);
}
btree_res[0] = start.elapsed().as_nanos() as f64 * (mega_items / 1_000_000) as f64;
btree_mem =
get_rss_bytes(&mut sys).saturating_sub(baseline) * (mega_items / 1_000_000) as u64;
}
let baseline = get_rss_bytes(&mut sys);
{
let start = Instant::now();
let mut coll = HashMap::new();
let items = 1_000_000;
for i in 0..items {
coll.insert(i as u64, i as u64);
}
hash_res[0] = start.elapsed().as_nanos() as f64 * (mega_items / 1_000_000) as f64;
hash_mem =
get_rss_bytes(&mut sys).saturating_sub(baseline) * (mega_items / 1_000_000) as u64;
}
println!(
"{:<15} | {:>15} | {:>12} | {:>12} | {:>15} | {:>12} | {:>12}",
"Pushing",
format_time(pico_res[0]),
format_time(vec_res[0]),
format_time(deque_res[0]),
format_time(list_res[0]),
format_time(btree_res[0]),
format_time(hash_res[0])
);
println!(
"{:<15} | {:>15} | {:>12} | {:>12} | {:>15} | {:>12} | {:>12}",
"Access (avg)",
format_time(pico_res[1]),
format_time(vec_res[1]),
format_time(deque_res[1]),
"> 1 WEEK",
"O(log N)",
"O(1)"
);
println!(
"{:<15} | {:>15} | {:>12} | {:>12} | {:>15} | {:>12} | {:>12}",
"RAM Usage",
format_bytes(pico_mem),
format_bytes(vec_mem),
format_bytes(deque_mem),
format_bytes(list_mem),
format_bytes(btree_mem),
format_bytes(hash_mem)
);
println!("{:-<145}", "");
let scales = [
("1 GB", 125_000_000),
("2 GB", 250_000_000),
("3 GB", 375_000_000),
("4 GB", 500_000_000),
];
for (scale_label, items) in scales {
println!(
"\n--- SENSITIVITY TEST FOR {} ({} Items) ---",
scale_label, items
);
println!("{:-<100}", "");
println!(
"{:<20} | {:>15} | {:>15} | {:>15} | {:>15}",
"N (Chunk Size)", "Push", "Access", "Update", "RAM Usage"
);
println!("{:-<100}", "");
let indices: Vec<usize> = (0..20_000).map(|i| (i * 13) % items).collect();
run_sens::<128>(&mut sys, items, &indices, "128 (1 KB)");
run_sens::<1024>(&mut sys, items, &indices, "1024 (8 KB)");
run_sens::<8192>(&mut sys, items, &indices, "8192 (64 KB)");
run_sens::<32768>(&mut sys, items, &indices, "32768 (256 KB)");
run_sens::<65536>(&mut sys, items, &indices, "65536 (512 KB)");
run_sens::<131072>(&mut sys, items, &indices, "131072 (1 MB)");
run_sens::<262144>(&mut sys, items, &indices, "262144 (2 MB)");
run_sens::<655360>(&mut sys, items, &indices, "655360 (5 MB)");
run_sens::<2097152>(&mut sys, items, &indices, "2097152 (16 MB)");
{
let baseline = get_rss_bytes(&mut sys);
let start = Instant::now();
let mut coll = Vec::new();
for j in 0..items {
coll.push(black_box(j as u64));
}
let push_time = start.elapsed().as_nanos() as f64;
let mem = get_rss_bytes(&mut sys).saturating_sub(baseline);
let start = Instant::now();
for &idx in &indices {
black_box(coll.get(idx));
}
let get_time = start.elapsed().as_nanos() as f64 / indices.len() as f64;
let start = Instant::now();
for &idx in &indices {
if let Some(v) = coll.get_mut(idx) {
*v = black_box(*v + 1);
}
}
let mut_time = start.elapsed().as_nanos() as f64 / indices.len() as f64;
println!("{:-<100}", "");
println!(
"{:<20} | {:>15} | {:>15} | {:>15} | {:>15}",
"Std Vec Ref",
format_time(push_time),
format_time(get_time),
format_time(mut_time),
format_bytes(mem)
);
black_box(coll);
}
println!("{:-<100}", "");
}
}
fn run_sens<const N: usize>(sys: &mut System, items: usize, indices: &[usize], label: &str) {
let baseline = get_rss_bytes(sys);
let start = Instant::now();
let mut pico = PicoList::<u64, N>::new();
for i in 0..items {
pico.push(black_box(i as u64));
}
let push_time = start.elapsed().as_nanos() as f64;
let mem = get_rss_bytes(sys).saturating_sub(baseline);
let start = Instant::now();
for &idx in indices {
black_box(pico.get(idx));
}
let get_time = start.elapsed().as_nanos() as f64 / indices.len() as f64;
let start = Instant::now();
for &idx in indices {
if let Some(v) = pico.get_mut(idx) {
*v = black_box(*v + 1);
}
}
let mut_time = start.elapsed().as_nanos() as f64 / indices.len() as f64;
println!(
"{:<20} | {:>15} | {:>15} | {:>15} | {:>15}",
label,
format_time(push_time),
format_time(get_time),
format_time(mut_time),
format_bytes(mem)
);
black_box(pico);
}