hyperium / hyperium/hyper

Slow reading of small chunks

Open
#2,135 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-http1 C-performance
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.