Skip to content

perf(header): allocate once in HeaderValue::from for integers - #885

Open
patrick-immometrica wants to merge 1 commit into
hyperium:masterfrom
patrick-immometrica:perf-header-value-from-int
Open

patrick-immometrica wants to merge 1 commit into
hyperium:masterfrom
patrick-immometrica:perf-header-value-from-int

Conversation

@patrick-immometrica

Copy link
Copy Markdown

HeaderValue::from(u64) (and the other integer impls) format into a BytesMut::with_capacity(20) and then freeze() it. Since the buffer's len is smaller than its capacity, freeze() goes through From<Vec<u8>> for Bytes, which allocates a second Box<Shared>. So every call costs two allocations for a few bytes.

hyper calls this for content-length on every response, so it is a fairly hot path.

This formats with itoa::Buffer on the stack (itoa is already a dependency) and does Bytes::copy_from_slice, which is one exact-size allocation. Because the per-type max length isn't needed anymore, the three target_pointer_width blocks collapse into one list.

Numbers on an M-series Mac, HeaderValue::from(1024u64):

master this PR
allocations per call (counting allocator) 2 1
from_u64 bench (nightly, 3 runs) 33.0 / 37.2 / 32.6 ns 18.5 / 16.8 / 17.8 ns

I added the from_u64 bench to benches/src/header_value.rs. Existing tests cover the formatting for each type, and the behavior is unchanged.

One trade-off: the old value already had its Shared allocated, so cloning it was only a refcount bump. The new one is a plain promotable Bytes, so the first clone allocates the Shared instead. A value that gets cloned therefore ends up at the same two allocations as before, and one that isn't saves one.

The integer conversions wrote into a BytesMut sized for the longest
possible number and then froze it. Since the buffer is almost never
full, freeze() can't hand the Vec over directly and allocates a shared
header on top, so every conversion did two allocations. Copying the
itoa output straight into Bytes does one.

hyper uses this for content-length on every response with a known body
size.

Add a bench for HeaderValue::from(u64).
@patrick-immometrica
patrick-immometrica marked this pull request as ready for review November 5, 2026 11:09

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant