Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-threaded rust number of threads is way too low. #421

Open
svladykin opened this issue Feb 6, 2025 · 6 comments
Open

Multi-threaded rust number of threads is way too low. #421

svladykin opened this issue Feb 6, 2025 · 6 comments

Comments

@svladykin
Copy link

https://www.youtube.com/watch?v=2e9U5sQ835Y

For mutlithreaded rust this is way too low:
Environment="THREAD_COUNT=4"

Golang can use pretty much unlimited number of goroutines and rust has a hard limit of 4 threads. You have to make sure that rust app does not hit this limit to make it work reasonably. Set the number of threads to 100, maybe even more is needed. Otherwise this comparison makes no sense: we could set it to 2 threads and make it even slower and still could say that it is multi-threaded rust:)

@antonputra
Copy link
Owner

I tried 4, 10, 100, and 1000 numbers of threads; anything beyond 100 threads results in all sorts of errors. That example from the Rust book is not really designed to be scaled. I'll cover Tokio and Hyper in the following video.

@antonputra
Copy link
Owner

Simply using a thread pool won't solve any problems that Rust's std faces, you need an async runtime for that.

@svladykin
Copy link
Author

Are you saying that having 10 or 100 threads is no better than 4? This is very suspicious because CPU was clearly underutilized for multi-threaded rust in your video and that is where thread pool size increase should actually help.

I'm more into Golang and don't have much idea about Rust at the same time I don't believe Rust is so broken.

@SvetlinZarev
Copy link
Contributor

SvetlinZarev commented Feb 7, 2025

The main issue was that the rust toy server did not support HTTP keep-alive requests. When I disabled keep-alive in JMeter, I got the same number of requests per second (around 7k, regardless of number of client/server threads) in Golang as in Rust. After implementing very rudimentary keep-alive support, I again have the same RPS in both languages - around 60k.

The difference this time is that Go has around 500% (5cpus) utilisation, while Rust - only 140% :)

For some reason I failed to achieve 100% CPU saturation regardless of how many threads and I always hit the same limit - around 60k with both apps. SO I guess I'm either hitting a JMeter limit or a MacOS one.

Either way, the mystery is solved :) and Rust has the better performance (RPS per CPU utilization).

Also please keep in mind that the Rust version is not a real HTTP server, but a very basic toy one. Rust (for a very good reason) does not have a HTTP server in its standard library, and it's not possible to implement one in a couple of dozen lines of code. This comparison is in interesting one, but does not reflect in any way using rust in production.

@amitaid
Copy link
Contributor

amitaid commented Feb 7, 2025

There are several other issues with this test.
Using stdlib for both languages ignores a lot of improvements available from community libraries. Go's stdlib server, while standard, isn't exactly known for performance, and as @SvetlinZarev pointed out, Rust's stdlib doesn't have one and the current implementation doesn't actually do any server work like routing, and only confirms the HTTP request line matches the expected path.
The same goes for the chosen Go json lib (json-iterator), when there are faster alternatives (like bytedance/sonic).

I think a better approach is test two/three Go frameworks (Gin, fiber and maybe go/web ) using bytedance/sonic, and two/three Rust frameworks (actix, axum, and may_minhttp).

I think this test would be closer to peak performance for each language, with the caveat that this is a very simple test that doesn't really reflect real-world use-cases.

@LtdJorge
Copy link
Contributor

Goroutines are not threads. Go can run a million goroutines on one thread, same as Rust can do millions of async tasks in one thread. The advantage of goroutines vs the Rust coroutines generated by the compiler is that goroutines are scheduled by the Go runtime itself, and are preemptive. Rust doesn't include a scheduler and relies on the likes of Tokio, which are cooperative schedulers.

If you want to get closer to Go's level of concurrency without using async Rust, use something like corosensei or may (coroutines) and build your own scheduler. These will still be cooperatively scheduled, though, not preemptively like in Erlang. In Erlang you can run a blocking process that consumes 100% of CPU indefinitely and all other processes continue to make progress, that's the beauty of preemption.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants