Hacker News new | ask | show | jobs
by koakuma-chan 439 days ago
In Rust all web frameworks are fast because they all use the same stack tokio + hyper.
3 comments

By the way, Ferron web server also uses Tokio and Hyper.
Fun experiment, try and benchmark a simple file download on a simple web server using rocket.rs (which also uses tokio and hyper, and has a built in fileserver type, so just a few lines of code) and compare it to a vanilla nginx config.

Here’s the rocket.rs source I used:

    #[rocket::main]
    async fn main() -> Result<(), rocket::Error> {
        rocket::build()
            .mount("/", rocket::fs::FileServer::from("/tmp/test"))
            .ignite()
            .await?
            .launch()
            .await?;
        Ok(())
    }
And do `mkdir /tmp/test && dd if=/dev/urandom of=/tmp/test/bigfile bs=1G count1` to create a 1G file, and run `time curl -o /dev/null localhost:8000/bigfile`.

My nginx config:

    worker_processes auto;
    master_process off;
    pid /dev/null;

    events {}

    http {
        sendfile on;
        access_log /dev/stdout;
        error_log /dev/stderr;
        server {
            listen 8089;
            server_name localhost;
            root /tmp/test;

            location / {
                try_files $uri $uri/ =404;
            }
        }
    }
launched with `nginx -c "$(pwd)"/nginx.conf -g "daemon off;"`

The results for a 1GB file for me on an nvme ssd, averaged over 100 runs:

    nginx: 150ms
    rocket: 4 seconds
Or roughly 25x slower. Release mode makes no difference.

You can definitely write slow code in rust if you’re naive about reading/writing between channels a few kilobytes at a time, which is what rocket does, vs using sendfile(2), like nginx does.

Edit: These numbers were from a few months ago... I tried it again by just pasting the above into a new project with `cargo init` and adding rocket and tokio to my deps, and it's now 2.3s in debug and 1.2s in release mode. It may have improved since a few months ago, but it's still 10x slower.

This doesn't count because hyper doesn't support sendfile. Maybe hyper would outperform nginx in other scenarios.
Disabling sendfile in nginx makes it take 170ms instead of 150ms on my system.

Because nginx knows to tune the buffer sizes properly, which goes a long way.

Using strace reveals what’s happening, rocket is reading and writing to file descriptors 4 kilobytes at a time, using 2 syscalls every chunk. nginx uses far, far fewer of them. (And with sendfile enabled, only one for the whole download.)

Also, there’s no reason rocket can’t use sendfile too. It’s basically the theoretically fastest way to perform this operation, and IMO rocket ought to use it by default if it’s available on the OS.

> Because nginx knows to tune the buffer sizes properly, which goes a long way.

Interesting. Do you know which algorithm nginx uses to determine the proper buffer size?

Enabling lto, using codegen-units=1, and using the minimum required feature flags might improve performance a bit.
No, it won’t. Rocket is issuing 2 syscalls for every 4 kilobytes, which is what is killing the performance.
Hold my beer while I trivially write a slow web server on tokio + hyper.
I could write one without Tokio, I'd just poll the sockets at 100 Hz
Why not two birds with one stone: do the same in tokio + hyper, and disprove the ridiculous claim!
Holding :>