Standard Collections
Collection Performance Trade-Offs
Asymptotic Big-O notation hides constant factors—such as CPU cache locality, memory allocator pressure, hashing cost, and branch prediction—that often determine real-world collection performance on modern hardware.
1. Small $N$: Sorted or Linear Vec<(K, V)> vs. HashMap<K, V>
For small maps (typically $N \le 15\text{–}30$ entries with small keys like integers or short strings), a flat Vec<(K, V)> scanned linearly with .iter().find(...) or searched with .binary_search_by_key(...) often outperforms HashMap<K, V>:
- Scanning 16 contiguous
(u32, u32)pairs reads just two 64-byte L1 cache lines with hardware prefetching and zero SipHash computation. - A
Vec<(K, V)>has zero control-byte or load-factor slack overhead.
struct SmallMap<K, V> {
entries: Vec<(K, V)>,
}
impl<K: Eq, V> SmallMap<K, V> {
fn new() -> Self {
Self { entries: Vec::new() }
}
fn insert(&mut self, key: K, value: V) {
if let Some((_, existing)) = self.entries.iter_mut().find(|(k, _)| *k == key) {
*existing = value;
} else {
self.entries.push((key, value));
}
}
fn get(&self, key: &K) -> Option<&V> {
self.entries
.iter()
.find_map(|(k, v)| if k == key { Some(v) } else { None })
}
}
fn main() {
let mut headers = SmallMap::new();
headers.insert("content-type", "application/json");
headers.insert("accept", "*/*");
println!("content-type: {:?}", headers.get(&"content-type"));
}
content-type: Some(&"application/json")
2. Memory Overhead per Element
| Collection | Per-Element Memory Overhead | Allocation Pattern |
|---|---|---|
Vec<T> / VecDeque<T> / BinaryHeap<T> |
0 bytes (only unused capacity slack) | Single contiguous heap allocation |
HashMap<K, V> / HashSet<T> |
~1 byte control tag + ~12.5%–50% load-factor slack | Single contiguous heap allocation |
BTreeMap<K, V> / BTreeSet<T> |
Node pointers + unused slots in B=6 nodes |
One heap allocation per 6–11 elements |
LinkedList<T> |
16 bytes (prev + next pointers) + allocator header |
One heap allocation per element |
3. Hashing Overhead vs. Comparison Overhead
When choosing between HashMap<K, V> and BTreeMap<K, V> for large $N$:
- Long string keys (
String) with shared prefixes (such as URLs"https://example.com/api/v1/..."):BTreeMapmust compare the shared prefix at every level of the tree ($O(\log n)$ comparisons), whereasHashMaphashes the string once and compares full keys only on the matching bucket. - Small integer keys (
u32,u64): Default SipHash-1-3 has a fixed initialization and finalization cost that can exceed simple integer comparisons. If keys are not attacker-controlled, using a fast non-cryptographic hasher (orBTreeMapif sorted order is useful) eliminates that hashing bottleneck.