use criterion::{Criterion, criterion_group, criterion_main}; use std::collections::HashMap as StdHashMap; use std::hint::black_box; use hashbrown::HashMap as BrownHashMap; const SIZES: [usize; 3] = [5, 10, 50]; const QUERY_COUNT: usize = 4096; /// Детерминированный ключ ~17 символов, например "key_9e3779b9_0042". fn make_key(i: u64) -> String { let h = i.wrapping_mul(0x9E37_79B9_7F4A_7C15); format!("key_{:08x}_{:04}", h & 0xFFFF_FFFF, i) } // Для длинных ключей (пути/URL) замени на: // fn make_key(i: u64) -> String { // format!( // "/api/v2/users/{i}/settings/visibility_{:08x}", // i.wrapping_mul(0x9E37_79B9) // ) // } fn make_vec(n: usize) -> Vec<(String, u64)> { (0..n as u64).map(|i| (make_key(i), i)).collect() } fn make_std_map(data: &[(String, u64)]) -> StdHashMap { data.iter().map(|(k, v)| (k.clone(), *v)).collect() } fn make_brown_map(data: &[(String, u64)]) -> BrownHashMap { data.iter().map(|(k, v)| (k.clone(), *v)).collect() } fn make_queries(n: usize, with_misses: bool) -> Vec { let keys: Vec = (0..n as u64).map(make_key).collect(); let missing = "__missing_key__".to_string(); (0..QUERY_COUNT) .map(|i| { if with_misses && i % 16 == 15 { missing.clone() } else { let idx = i.wrapping_mul(2_654_435_761) % n; keys[idx].clone() } }) .collect() } fn lookup_std(map: &StdHashMap, queries: &[String]) -> u64 { let mut sum = 0u64; for q in queries { match map.get(black_box(q.as_str())) { Some(v) => sum = sum.wrapping_add(*v), None => sum = sum.wrapping_add(1), } } black_box(sum) } fn lookup_brown(map: &BrownHashMap, queries: &[String]) -> u64 { let mut sum = 0u64; for q in queries { match map.get(black_box(q.as_str())) { Some(v) => sum = sum.wrapping_add(*v), None => sum = sum.wrapping_add(1), } } black_box(sum) } fn lookup_vec(data: &[(String, u64)], queries: &[String]) -> u64 { let mut sum = 0u64; for q in queries { let q = black_box(q); match data.iter().find(|(k, _)| k == q) { Some((_, v)) => sum = sum.wrapping_add(*v), None => sum = sum.wrapping_add(1), } } black_box(sum) } fn bench(c: &mut Criterion) { let mut group = c.benchmark_group("map_vs_vec_str"); for n in SIZES { let vec_data = make_vec(n); let std_map = make_std_map(&vec_data); let brown_map = make_brown_map(&vec_data); let queries_hit = make_queries(n, false); let queries_mixed = make_queries(n, true); // std::collections::HashMap (SipHash) group.bench_function(format!("std_hashmap_hit/{n}"), |b| { b.iter(|| lookup_std(black_box(&std_map), black_box(&queries_hit))) }); group.bench_function(format!("std_hashmap_mixed/{n}"), |b| { b.iter(|| lookup_std(black_box(&std_map), black_box(&queries_mixed))) }); // hashbrown (SwissTable + foldhash) group.bench_function(format!("hashbrown_hit/{n}"), |b| { b.iter(|| lookup_brown(black_box(&brown_map), black_box(&queries_hit))) }); group.bench_function(format!("hashbrown_mixed/{n}"), |b| { b.iter(|| lookup_brown(black_box(&brown_map), black_box(&queries_mixed))) }); // Vec<(String, u64)>, линейный поиск group.bench_function(format!("vec_linear_hit/{n}"), |b| { b.iter(|| lookup_vec(black_box(&vec_data), black_box(&queries_hit))) }); group.bench_function(format!("vec_linear_mixed/{n}"), |b| { b.iter(|| lookup_vec(black_box(&vec_data), black_box(&queries_mixed))) }); } group.finish(); } criterion_group!(benches, bench); criterion_main!(benches);