From a6413a4677aa50e05d11eb9faa7e2cff4466aabf Mon Sep 17 00:00:00 2001 From: DioCrafts Date: Tue, 3 Mar 2026 19:23:17 +0100 Subject: [PATCH 1/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, } } From 3b36b3050f1c1f14a294585d5c52bd67da1741db Mon Sep 17 00:00:00 2001 From: Dionisio Date: Tue, 3 Mar 2026 20:20:29 +0100 Subject: [PATCH 2/2] fix: add overflow guard in advance() and correct comment in copy_to_bytes() --- src/common/buf.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/buf.rs b/src/common/buf.rs index ed4a5d7574..91d9f22250 100644 --- a/src/common/buf.rs +++ b/src/common/buf.rs @@ -42,6 +42,7 @@ impl Buf for BufList { #[inline] fn advance(&mut self, mut cnt: usize) { + assert!(cnt <= self.remaining, "`cnt` greater than remaining"); self.remaining -= cnt; while cnt > 0 { { @@ -92,7 +93,7 @@ impl Buf for BufList { _ => { assert!(len <= self.remaining, "`len` greater than remaining"); let mut bm = BytesMut::with_capacity(len); - // Note: `self.take(len)` calls `self.advance()` internally, + // Note: `bm.put(self.take(len))` calls `self.advance()` internally, // which already decrements `self.remaining`. bm.put(self.take(len)); bm.freeze()