Conversation
The HTTP/1 write strategy was chosen once per connection, purely from `AsyncWrite::is_write_vectored()`: vectored IO always queued, everything else always flattened. Vectored writes avoid copying the body, but each extra `IoSlice` costs bookkeeping in `BufList`, in `chunks_vectored`, and in the kernel's gather loop, so for small bodies queueing is a net loss against a single `memcpy` plus one `write`. Add a `WriteStrategy::Auto` variant, now the default when the IO supports vectored writes, that decides per buffer: flatten anything below `AUTO_FLATTEN_LIMIT`, queue the rest. Once something is queued, later buffers must queue too, or they would be written ahead of the bytes that came first. `AUTO_FLATTEN_LIMIT` is 16KB, from `benches/h1_writev.rs` (added here), which serves a fixed-size response body over loopback TCP with each strategy forced on. On x86_64 Linux the crossover sits around 20-24KB; 16KB is the nearest power of two below it. No public API change. `http1::Builder::writev` still overrides the default in either direction. Closes hyperium#2910
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2910.
Problem
Buffered::newpicks the HTTP/1 write strategy once per connection, purely fromAsyncWrite::is_write_vectored():Vectored writes avoid copying the body, but they are not free — each extra
IoSlicecosts bookkeeping inBufList, inchunks_vectored, and in the kernel'swritevgather loop, and queueing also gives uppoll_flush's specialized single-writepath. For a small body that overhead is larger than thememcpyit saves, so today every vectored-capable connection pays it regardless of body size.Change
Adds a
WriteStrategy::Autovariant, now the default when the IO supports vectored writes (non-vectored IO still goes straight toFlatten).WriteBuf::bufferresolves it per buffer: flatten anything belowAUTO_FLATTEN_LIMIT, queue the rest.Auto is resolved per buffer rather than latched on the first one, which is a small deviation from the issue's sketch. Latching would let one connection's first chunk fix the strategy for every later response on that keep-alive connection — a small first body would permanently pessimize a later 10MB one. Deciding per buffer costs one comparison and gets both cases right.
One ordering constraint:
WriteBuf'sBufimpl always yieldsheadersbeforequeue, so once the queue is non-empty, a later buffer must queue too or it would be written ahead of bytes that came first.should_flattenchecks!self.queue.has_remaining()for exactly this, andwrite_buf_auto_keeps_order_after_queueingcovers it.poll_flushnow askswrite_buf.is_flattened()instead of matching onFlattendirectly, so anAutobuffer that ended up entirely inheadersstill takes the plain-writepath — that's where most of the win comes from.No public API change.
http1::Builder::writev(bool)still overrides the default in either direction on both client and server.Picking the constant
Added
benches/h1_writev.rs: it serves a single fixed-size response body over a loopback TCP connection, once withwritev(true)and once withwritev(false), across body sizes from 64B to 1MB. On an x86_64 Linux machine (median of 3 runs, ns/iter, lower is better):The crossover sits around 20–24KB.
AUTO_FLATTEN_LIMITis 16KB, the nearest power of two below it, staying on the conservative side. The derivation is documented on the constant.The bench also has an
automode that leaves the default in place. With this change it tracks whichever forced strategy is faster at every size — e.g. 1MB goes 95,820 (flatten) / 77,542 (queue) / 76,607 (auto), and 64B goes 5,660 (flatten) / 5,771 (queue) / 5,670 (auto).Trade-off worth flagging
Flattening copies into the
headersVec, whose capacity is retained across requests (Cursor::resetclears but does not shrink). A vectored-capable connection that serves a ~16KB body will now hold onto a ~16KB buffer for its lifetime, where before it held only the header bytes. This is bounded byAUTO_FLATTEN_LIMITand is already the status quo for every non-vectored connection (e.g. most TLS streams), but it is a real change for servers holding many idle keep-alive connections. Happy to lower the limit if you'd rather trade some of the throughput for that.Testing
proto::h1::iocovering small-buffer flattening, large-buffer queueing, ordering after a queue, and thatAutoflushes small writes in a singlewritecall.cargo test --features full— 9 test binaries, 316 tests, 0 failures.cargo fmt --checkandcargo clippy --features full -- -D warningsclean.