Hyper / Tonic client performance

Hi Guys!

I tested the helloworld example from the hyperium/Tonic examples, and I did rewrite the client to check the performance.
I applied a for loop, and spawn async all the request.

I built both the server and client in release mode.
I was surprised to see, that it took ~1 sec per 1.000 request, and the client process generated a 100% cpu load, while the server consumed 10-15% cpu load.

Am I doing something wrong?

I expected a much faster client performance, and I do not know why the client uses that much more CPU then the server.

Thank you in advance!

Here is my code:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = GreeterClient::connect("http://[::1]:50051").await?;

    let mut tasks = Vec::new();

    for i in 0..100000 {
        let mut _client = client.clone();
        tasks.push(tokio::spawn(async move {
            send(_client, i).await;
        }));
    }

    future::join_all(tasks).await;
    Ok(())
}

async fn send(mut _client: GreeterClient<Channel>, i: usize) {
    let req = tonic::Request::new(HiRequest {
        to: format!("{}", i),
    });
    let _ = _client.say_hi(req).await;
}

So the strange thing to me, is not the server is the bottleneck. Client ate a 100% cpu, the server only 10-15%, and the client process seems to be parallel and async, and slow. Why?

Both the server and client did run on my laptop at the same time.

It's because join_all has quadratic time complexity due to a poor implementation in the futures crate. Use a loop instead.

for task in tasks {
    task.await.expect("panic in task");
}
5 Likes

You made my day again. Thank you! I have just tried your advice, and it did mean a 10x performance boost.

1 Like