Skip to content

return more data at once from TlsStream::poll_read - #198

Open
trinity-1686a wants to merge 3 commits into
rustls:mainfrom
trinity-1686a:hyper-perf
Open

return more data at once from TlsStream::poll_read#198
trinity-1686a wants to merge 3 commits into
rustls:mainfrom
trinity-1686a:hyper-perf

Conversation

@trinity-1686a

Copy link
Copy Markdown

When using Hyper with tokio-rustls, and streaming a response body, hyper only emits blocks of 16k (a single TLS record), even when hyper uses a larger receive buffer. This causes more wake-ups for hyper callers, and generally more work inside hyper itself.

This PR makes it so TlsStream::poll_read tries to return more data at once, which measurably improves Hyper's performance over TLS streams.

I assume there is a similar gain on the server side with requests that have a large body (instead of response having a body for client), though i haven't attempted to measure or fix that yet.

very simple (but noisy) bench setup:
running a slightly simplified version of this example (commenting out to_bytes() and actual printing of the body, to make the gain more visible), and running perf stat ./target/release/examples/client https://proof.ovh.net/files/1Gb.dat (download 1GiB from a remote server), i get

cpu-cycles:u
before: 3.6B
after: 2.7B

task-clock:u
before: 11.7s
after: 7.3s

While this could also be a change in hyper to call poll_read() until it returns Pending, i think other consumers of rustls could have the same inefficiency, and this is better handled at that level.

this can improve the performance of some consumers, notably Hyper

@djc djc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

Comment thread src/client.rs Outdated
let len = data.len().min(buf.remaining());
buf.put_slice(&data[..len]);
self.consume(len);
if len > 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: suggest flipping this with an early return, to reduce rightward drift.

Comment thread src/client.rs Outdated
Poll::Ready(Err(_)) => break, // non-transient error gets re-emitted next poll
Poll::Pending => break,
};
let len = data.len().min(buf.remaining());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: using a symmetric min() like Ord::min() makes this easier to follow IMO.

@djc

djc commented Sep 1, 2026

Copy link
Copy Markdown
Member

Would be cool to also do this for the server side.

@trinity-1686a

Copy link
Copy Markdown
Author

i've confirmed the effect on the read path of the server.

i should also add because it bite me while trying to assess performance on the server half that, in hyper, this only impacts http1, h2 there seem to use much smaller buffers so that results are largely within the variance of my setup

@trinity-1686a trinity-1686a changed the title return more data at once from ClientTlsStream::poll_read return more data at once from TlsStream::poll_read Sep 1, 2026
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.

2 participants