From ae1e073ee9a1ae563708191826a793d03e409a7b Mon Sep 17 00:00:00 2001 From: DioCrafts Date: Tue, 3 Mar 2026 18:54:20 +0100 Subject: [PATCH 1/2] perf: eliminate ParserConfig clones on every H1 request - Change ParseContext.h1_parser_config from owned ParserConfig to &'a ParserConfig - Remove .clone() in Conn::poll_read_head() (conn.rs) - Remove .clone() in Buffered::parse() retry loop (io.rs) - Pre-allocate read buffer with INIT_BUFFER_SIZE instead of capacity(0) (io.rs) - Update all test call sites in role.rs and io.rs Eliminates 2 unnecessary ParserConfig copies per HTTP/1.1 request in the hot parsing path. --- src/proto/h1/conn.rs | 2 +- src/proto/h1/io.rs | 6 +++--- src/proto/h1/mod.rs | 2 +- src/proto/h1/role.rs | 40 ++++++++++++++++++++-------------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 3d71ed5bc5..94b2844538 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -239,7 +239,7 @@ where ParseContext { cached_headers: &mut self.state.cached_headers, req_method: &mut self.state.method, - h1_parser_config: self.state.h1_parser_config.clone(), + h1_parser_config: &self.state.h1_parser_config, h1_max_headers: self.state.h1_max_headers, preserve_header_case: self.state.preserve_header_case, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 6a30d73c84..de3a8eb4ff 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -68,7 +68,7 @@ where io, partial_len: None, read_blocked: false, - read_buf: BytesMut::with_capacity(0), + read_buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), read_buf_strategy: ReadStrategy::default(), write_buf, } @@ -182,7 +182,7 @@ where ParseContext { cached_headers: parse_ctx.cached_headers, req_method: parse_ctx.req_method, - h1_parser_config: parse_ctx.h1_parser_config.clone(), + h1_parser_config: parse_ctx.h1_parser_config, h1_max_headers: parse_ctx.h1_max_headers, preserve_header_case: parse_ctx.preserve_header_case, #[cfg(feature = "ffi")] @@ -704,7 +704,7 @@ mod tests { let parse_ctx = ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index a8f36f5fd9..1fea370cc5 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -71,7 +71,7 @@ pub(crate) struct ParsedMessage { pub(crate) struct ParseContext<'a> { cached_headers: &'a mut Option, req_method: &'a mut Option, - h1_parser_config: ParserConfig, + h1_parser_config: &'a ParserConfig, h1_max_headers: Option, preserve_header_case: bool, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index f92092e5a9..92cbcccb88 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -1674,7 +1674,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut method, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1702,7 +1702,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1726,7 +1726,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1747,7 +1747,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1770,7 +1770,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1797,7 +1797,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), - h1_parser_config, + h1_parser_config: &h1_parser_config, h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1821,7 +1821,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1849,7 +1849,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut method, - h1_parser_config, + h1_parser_config: &h1_parser_config, h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1876,7 +1876,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1896,7 +1896,7 @@ mod tests { let ctx = ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: true, #[cfg(feature = "ffi")] @@ -1935,7 +1935,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -1956,7 +1956,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -2186,7 +2186,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut Some(Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -2207,7 +2207,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut Some(m), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -2228,7 +2228,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut Some(Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -2798,7 +2798,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut Some(Method::GET), - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -2842,7 +2842,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: max_headers, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -2866,7 +2866,7 @@ mod tests { ParseContext { cached_headers: &mut None, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: max_headers, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -3035,7 +3035,7 @@ mod tests { ParseContext { cached_headers: &mut headers, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] @@ -3080,7 +3080,7 @@ mod tests { ParseContext { cached_headers: &mut headers, req_method: &mut None, - h1_parser_config: Default::default(), + h1_parser_config: &Default::default(), h1_max_headers: None, preserve_header_case: false, #[cfg(feature = "ffi")] From 35c85b981e1dbd6c2fede5e86d87c32acde922d9 Mon Sep 17 00:00:00 2001 From: DioCrafts Date: Tue, 3 Mar 2026 19:23:17 +0100 Subject: [PATCH 2/2] perf: make BufList::remaining() O(1) by caching total bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a field to BufList that tracks the total number of bytes across all buffers. This avoids iterating the entire VecDeque on every call to remaining(), which is invoked from hot paths like poll_flush, can_buffer, and advance. Previously, remaining() was O(n) where n is the number of buffers in the queue (up to 16 in Queue write strategy). Now it is O(1) — a simple field read. --- src/common/buf.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/common/buf.rs b/src/common/buf.rs index d00071551b..ed4a5d7574 100644 --- a/src/common/buf.rs +++ b/src/common/buf.rs @@ -5,18 +5,21 @@ use bytes::{Buf, BufMut, Bytes, BytesMut}; pub(crate) struct BufList { bufs: VecDeque, + remaining: usize, } impl BufList { pub(crate) fn new() -> BufList { BufList { bufs: VecDeque::new(), + remaining: 0, } } #[inline] pub(crate) fn push(&mut self, buf: T) { debug_assert!(buf.has_remaining()); + self.remaining += buf.remaining(); self.bufs.push_back(buf); } @@ -29,7 +32,7 @@ impl BufList { impl Buf for BufList { #[inline] fn remaining(&self) -> usize { - self.bufs.iter().map(|buf| buf.remaining()).sum() + self.remaining } #[inline] @@ -39,6 +42,7 @@ impl Buf for BufList { #[inline] fn advance(&mut self, mut cnt: usize) { + self.remaining -= cnt; while cnt > 0 { { let front = &mut self.bufs[0]; @@ -78,12 +82,18 @@ impl Buf for BufList { Some(front) if front.remaining() == len => { let b = front.copy_to_bytes(len); self.bufs.pop_front(); + self.remaining -= len; b } - Some(front) if front.remaining() > len => front.copy_to_bytes(len), + Some(front) if front.remaining() > len => { + self.remaining -= len; + front.copy_to_bytes(len) + } _ => { - assert!(len <= self.remaining(), "`len` greater than remaining"); + assert!(len <= self.remaining, "`len` greater than remaining"); let mut bm = BytesMut::with_capacity(len); + // Note: `self.take(len)` calls `self.advance()` internally, + // which already decrements `self.remaining`. bm.put(self.take(len)); bm.freeze() } @@ -100,6 +110,7 @@ mod tests { fn hello_world_buf() -> BufList { BufList { bufs: vec![Bytes::from("Hello"), Bytes::from(" "), Bytes::from("World")].into(), + remaining: 11, } }