From c05b68b64d607d4f9088211609b0b5c73d6a3cf3 Mon Sep 17 00:00:00 2001 From: patrick-immometrica Date: Thu, 5 Nov 2026 12:01:35 +0100 Subject: [PATCH] perf(header): skip the 12-slot step when a HeaderMap first grows A map from HeaderMap::new() starts with 6 usable slots and doubles from there, so a header block of 13-24 entries, which is common for browser requests, rehashes twice: 6 -> 12 -> 24. Going straight from 8 to 32 raw slots on the first grow makes that a single rehash and saves a pair of allocations. Maps of up to 6 entries are untouched. --- src/header/map.rs | 5 ++++- tests/header_map.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/header/map.rs b/src/header/map.rs index 6547114b..de17882d 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1846,7 +1846,10 @@ impl HeaderMap { self.entries = Vec::with_capacity(usable_capacity(new_raw_cap)); } else { let raw_cap = self.indices.len(); - self.try_grow(raw_cap << 1)?; + // Go from 6 to 24 usable slots in one step, so a header block of + // 7 to 24 entries is rehashed once instead of twice. + let new_raw_cap = if raw_cap == 8 { 32 } else { raw_cap << 1 }; + self.try_grow(new_raw_cap)?; } } diff --git a/tests/header_map.rs b/tests/header_map.rs index 77ab138c..786a5011 100644 --- a/tests/header_map.rs +++ b/tests/header_map.rs @@ -35,6 +35,16 @@ fn smoke() { } } +#[test] +fn first_grow_goes_to_24() { + let mut headers = HeaderMap::new(); + for i in 0..7 { + let name = HeaderName::from_bytes(format!("x-{}", i).as_bytes()).unwrap(); + headers.insert(name, HeaderValue::from_static("v")); + } + assert_eq!(headers.capacity(), 24); +} + #[test] #[should_panic] fn reserve_over_capacity() {