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.
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.
Interesting. Do you know which algorithm nginx uses to determine the proper buffer size?