Slow reading of small chunks
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 16.3k
- Forks
- 1.8k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 14
Description
While dealing with a performance library in requests (yes, the Python library) I stumbled across https://github.com/psf/requests/issues/2371. I wanted to quickly evaluate whether switching to Rust would make sense for my problem so I created a small benchmark in Rust, matching the ones in https://github.com/alex/http-client-bench:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::new();
let uri = "http://localhost:8080".parse()?;
let mut resp = client.get(uri).await?;
let mut handle = stdout();
while let Some(chunk) = resp.body_mut().data().await {
handle.write_all(&chunk?).await?;
}
Ok(())
}
To my surprise this is a lot slower than the Python clients on my machine:
$ ./run.sh
Python 3.7.4
go version go1.13.8 darwin/amd64
BENCH HTTPLIB:
8.00GiB 0:00:25 [ 317MiB/s] [================================>] 100%
BENCH URLLIB3:
8.00GiB 0:00:33 [ 244MiB/s] [================================>] 100%
BENCH REQUESTS
8.00GiB 0:00:36 [ 222MiB/s] [================================>] 100%
BENCH GO HTTP
8.00GiB 0:00:23 [ 351MiB/s] [================================>] 100%
signal: broken pipe
BENCH RUST HYPER
8.00GiB 0:00:59 [ 136MiB/s] [================================>] 100%
Error: Os { code: 32, kind: BrokenPipe, message: "Broken pipe" }
I've build it with --release, timings vary between runs but it's always the same ballpark. I thought at first that maybe it's just writing to stdout that's slow but even if I modify the benchmark to remove writing to stdout it gets faster but remains slower than the competition:
extern crate hyper;
use hyper::Client;
use hyper::body::HttpBody as _;
// use tokio::io::{stdout, AsyncWriteExt as _};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::new();
let uri = "http://localhost:8080".parse()?;
let mut resp = client.get(uri).await?;
// let mut handle = stdout();
let bench_size: usize = 8 * 1024 * 1024 * 1024;
let mut bytes_seen: usize = 0;
while let Some(chunk) = resp.body_mut().data().await {
bytes_seen += chunk?.len();
if bytes_seen >= bench_size { break; }
// handle.write_all(&chunk?).await?;
}
Ok(())
}
$ time ./target/release/http-client-bench
real 0m47.529s
user 0m31.867s
sys 0m34.785s
I've tried search the documentation for any configuration changes I might be able to make but nothing looked relevant as far as I could see. So... what's going on?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the Rust benchmark from the issue, including the version that reads resp.body_mut().data() without writing to stdout, and compare it with run.sh. Trace the hyper::Client response-body path to identify the bottleneck; done means explaining the discrepancy and demonstrating an improvement with the benchmark.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 32/100