Why Rust makes significantly slower web request than Go?

I have two pieces of codes, one in Go and another in Rust, and they both send GET request to retrieve content from a link.

Go Code:

package main

import (
	"fmt"
	"github.com/go-resty/resty/v2"
	"time"
)
func main() {
	start := time.Now()
	client := resty.New()
	resp, err:=client.R().Get(`https://www.pinterest.com/pin/378724649895155348/`)
	if err!=nil{
		_=resp.String()
	}else {
		fmt.Println(err)
	}
	duration:=time.Since(start)
	fmt.Println("Test Request Time Elapsed in Go: ",duration.Milliseconds(), "Milliseconds")
}

Rust Code:

use std::time::{Instant};
use reqwest;
fn main() {
    let now = Instant::now();

    let req_url="https://www.pinterest.com/pin/378724649895155348/";  
    let client = reqwest::blocking::Client::builder().build().unwrap();
    let result = client
        .get(req_url)
        .send();


    match result {
        Ok(response) => {
            let res_str=response.text().unwrap();    
        }
        Err(err) => {
            println!("{:?}",err)
        }
    }
    println!("Test Request Time Elapsed in Rust: {:?} Milliseconds", now.elapsed().as_millis());
}

The Go requests took 500-700 milliseconds in my computer, while the Rust request took 800-1100 milliseconds. I have run the test around 20 times, and Go is faster, every single time. This does make me wonder why, considering Rust is supposed to be faster than Go.

Are you building in release mode?

There's the footgun of not using --release when building/running for speed. Rust's default is to run up to 100 times slower due to unoptimized code and debug assertions.

But apart from that, this is not testing much of the language's speed. In HTTP requests majority of the time is spent waiting for the network, in system libraries handling TLS, or in the operating system kernel. It's 1% language, 99% just details how a particular request library is configured.

Rust's advantage is more in computation-heavy or memory-hungry operations. If you needed to make lots of requests and process their results, and carefully tuned the implementation, Rust could use less memory, and maybe have more consistent latency or use less CPU (thanks to lower-level control it gives over memory management).

But if all you need to do is a few requests, then Go is very good at it too, and even Python may be equally fast.

2 Likes

You are also benchmarking the time it takes to create the client. Shouldn't it be only the request?

I tried to build with --release and the result is not that much different. I guess it's just how things are. I want to make multiple requests, as fast as possible, and I suppose Go is a better choice.

On that note, I tried a different library, hyper, and I was able to achieve faster result than Go. So, I guess it's just the matter of which library to use.

I don't think debug or release build makes any significant difference. Most of the time here is spent waiting on the request round trip over the net. Which includes whatever time it take Pinterest to do it's thing.

Similarly adding the creation of the client is insignificant at this moment.

When I build and run that code I get :

use std::time::{Instant};

fn main() {
    let req_url="https://www.pinterest.com/pin/378724649895155348/";  
    let client = reqwest::blocking::Client::builder().build().unwrap();

    let now = Instant::now();
    let result = client
        .get(req_url)
        .send();

    match result {
        Ok(response) => {
            let _res_str=response.text().unwrap();    
        }
        Err(err) => {
            println!("{:?}",err)
        }
    }
    println!("Test Request Time Elapsed in Rust: {:?} Milliseconds", now.elapsed().as_millis());
}
✗ cargo run  --release
   Compiling rust-reqwest v0.1.0 (/Users/michaael/rust-reqwest)
    Finished release [optimized] target(s) in 0.45s
     Running `target/release/rust-reqwest`
Test Request Time Elapsed in Rust: 581 Milliseconds

Which puts it in the same execution time range of your Go numbers.
I'm using a 10 year of Intel PC running Win 10 for this so I doubt that is speeding anything up.

When I change the URL to one of my own pages I can get that time down to 300ms. Significantly faster than even your Go result. But change it to the URL of this thread and it's up to 1700ms.

Be careful what you are measuring. You are not actually measuring the speed of execution of the code making the request here. You are measuring network latency and server processing time.

4 Likes

Interestingly though when I make the same request with curl it is much faster:

✗ cargo run  --release
    Finished release [optimized] target(s) in 0.03s
     Running `target/release/rust-reqwest`
Test Request Time Elapsed in Rust: 607 Milliseconds
✗ curl -so /dev/null https://www.pinterest.com/pin/378724649895155348
dnslookup: 0.001689 | connect: 0.020213 | appconnect: 0.073785 | pretransfer: 0.073903 | starttransfer: 0.182357 | total: 0.182721 | size: 20
✗ 

On the other hand Chrome tells me it's taking nearly 500ms to make that download.

Well, if I run Go and Rust code on the same PC, same link, multiple times, shouldn't the network request time be the same for both piece of code.
Also, I can confirm my network suck.

Interesting!

There could be a number of reasons for this, so it's not a simple thing to test. Variables include stuff like

  • default window sizes
  • tls negotiation
  • time it takes for a client to initialise
  • connection pooling
  • http2 vs http

If you want to do lots of requests, it's definitely a good idea to run the test multiple times in a loop. Or run them in concurrently :slight_smile: This is because the cost of setting up the connection (tls etc) is only a factor for the first connections in the pool.

I tried hitting pinterest in a loop, but they quickly thottled my IP, so that test didn't go so well. Here are my results for hitting www.google.com on https, in a loop.

Test Request Time Elapsed in Go:  110 Milliseconds
Test Request Time Elapsed in Go:  120 Milliseconds
Test Request Time Elapsed in Go:  56 Milliseconds
Test Request Time Elapsed in Go:  59 Milliseconds
Test Request Time Elapsed in Go:  56 Milliseconds
Test Request Time Elapsed in Go:  60 Milliseconds
Test Request Time Elapsed in Go:  55 Milliseconds
Test Request Time Elapsed in Go:  75 Milliseconds
Test Request Time Elapsed in Go:  64 Milliseconds
Test Request Time Elapsed in Go:  51 Milliseconds

And in Rust

Test Request Time Elapsed in Rust: 125 Milliseconds
Test Request Time Elapsed in Rust: 59 Milliseconds
Test Request Time Elapsed in Rust: 56 Milliseconds
Test Request Time Elapsed in Rust: 59 Milliseconds
Test Request Time Elapsed in Rust: 60 Milliseconds
Test Request Time Elapsed in Rust: 53 Milliseconds
Test Request Time Elapsed in Rust: 60 Milliseconds
Test Request Time Elapsed in Rust: 55 Milliseconds
Test Request Time Elapsed in Rust: 56 Milliseconds
Test Request Time Elapsed in Rust: 51 Milliseconds

These results are almost identical -- we're just measuring the network time, really.

func main() {
	client := resty.New()

	for i := 0; i < 10; i++ {
		start := time.Now()
		//resp, err := client.R().Get(`https://www.pinterest.com/pin/378724649895155348/`)
		resp, err := client.R().Get(`https://www.google.com`)
		if err == nil {
			_ = resp.String()
		} else {
			fmt.Println(err)
		}
		duration := time.Since(start)
		fmt.Println("Test Request Time Elapsed in Go: ", duration.Milliseconds(), "Milliseconds")
	}
}
#[tokio::main]
async fn main() {
    //let req_url = "https://www.pinterest.com/pin/378724649895155348/";
    let req_url = "https://www.google.com";
    let client = reqwest::Client::builder().build().unwrap();

    for _ in 0..10 {
        let now = Instant::now();
        let result = client.get(req_url).send().await;
        match result {
            Ok(response) => {
                let _res_str = response.text().await.unwrap();
            }
            Err(err) => {
                println!("{:?}", err)
            }
        }
        let elapsed = now.elapsed();

        println!(
            "Test Request Time Elapsed in Rust: {:?} Milliseconds",
            elapsed.as_millis()
        );
    }
}

I'm using reqwest's async interface here, but blocking is the same in terms of latency. You'll want to use async if you're planning on doing a bunch of requests simultaneously, though.

I'm not sure this gives you a clear answer, but it looks like timings are equivalent when doing multiple requests and reusing the client. At least with www.google.com -- there might be some other stuff going on with pinterest. Maybe try hitting some other sites too :slight_smile:

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.