Live data from Hacker News

Comparison of Rust async and Linux thread context switch time and memory use

github.com

31–40 of 201 posts

Re: Comparison of Rust async and Linux thread context switch time and memory use

#31
post #4

Quickest summary: Rust async is >3x faster and lighter than Linux threads. This is a great accomplishment for Rust.

If you elide a bounds check from a function but still spend a billion cycles in a loop, you've made your code run ever so slightly faster but gained nothing in the big picture.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#32
post #4

Quickest summary: Rust async is >3x faster and lighter than Linux threads. This is a great accomplishment for Rust.

Keep in mind that a new async task doesn't create a new thread. So yes, "not creating a new thread" is 3x faster than "creating a thread". If the app layer can context switch using language level constructs, and do co-operative switching, then yes, one gets the 3x benefit. imho, whether the async executor and scheduler is performant enough to manage the tasks is what one should worry about.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#33
post #7

I am not surprised that the cost of context switching due to I/O readiness can often be roughly equal between async tasks and kernel threads. Normal blocking I/O can be surprisingly efficient because of various factors, such as a reduced need for system calls. Think about it this way—if you have a user-space thread which wakes up due to I/O readiness, then this means that the relevant kernel thread woke up from epoll…

Then why is it that IO-heavy benchmarks such as the Techempower web benchmark are dominated by async frameworks? The fastest results there are all from async frameworks [1]. And among Rust frameworks the same pattern holds. The fastest Rust frameworks are async while a synchronous frmework such as Rocket is about 20x slower. [1] https://www.techempower.com/benchmarks/#section=data-r20&hw=... [2] https://www.techempow…

Benchmarks are not everything, and the difference between asynch/synchronous operation is not the only thing the benchmark is testing (each of these different frameworks appear to have their own system for parsing and representing HTTP requests). You should know what usage patterns YOUR application sees, understand the relative cost of engineering time and CPU time for YOUR application, and do tests in YOUR environment.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#34
post #7

I am not surprised that the cost of context switching due to I/O readiness can often be roughly equal between async tasks and kernel threads. Normal blocking I/O can be surprisingly efficient because of various factors, such as a reduced need for system calls. Think about it this way—if you have a user-space thread which wakes up due to I/O readiness, then this means that the relevant kernel thread woke up from epoll…

I mostly agree with you (not the least of which is that blocking I/O is a damn fine API), but the reason that people use async I/O is to have lots of outstanding requests. Typically you would use select (or similar) to service whichever one responds first. That way you can multiplex many I/O streams onto a small number of threads. If threads are memory-intensive, you almost certainly have to do this.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#35
post #7

I am not surprised that the cost of context switching due to I/O readiness can often be roughly equal between async tasks and kernel threads. Normal blocking I/O can be surprisingly efficient because of various factors, such as a reduced need for system calls. Think about it this way—if you have a user-space thread which wakes up due to I/O readiness, then this means that the relevant kernel thread woke up from epoll…

Then why is it that IO-heavy benchmarks such as the Techempower web benchmark are dominated by async frameworks? The fastest results there are all from async frameworks [1]. And among Rust frameworks the same pattern holds. The fastest Rust frameworks are async while a synchronous frmework such as Rocket is about 20x slower. [1] https://www.techempower.com/benchmarks/#section=data-r20&hw=... [2] https://www.techempow…

Those benchmarks measure one very specific scenario: serving lots of small requests concurrently. Async handles that well because that's exactly the scenario where a single epoll_wait() call will return lots of events.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#36
post #7

I am not surprised that the cost of context switching due to I/O readiness can often be roughly equal between async tasks and kernel threads. Normal blocking I/O can be surprisingly efficient because of various factors, such as a reduced need for system calls. Think about it this way—if you have a user-space thread which wakes up due to I/O readiness, then this means that the relevant kernel thread woke up from epoll…

Google found that the main cost of context switching isn't really in the syscall boundary but in the task scheduling. That's why their linux fork has optional userspace scheduling of kernel threads with the switchto syscalls[0]. Essentially, if your thread already knows which thread should run next, it can context switch to it without having to schedule in the kernel, which is exactly the situation in these bucket brigade benchmarks.

This benchmark as written is probably underestimating kernel task scheduling cost since only 1 task is runnable at any 1 time, while a realistic multi-threaded system will have more runnable threads to juggle.

0: http://pdxplumbers.osuosl.org/2013/ocw//system/presentations...

Re: Comparison of Rust async and Linux thread context switch time and memory use

#38

Earlier quoted context omitted.

I read it as most people are over investing in async rust.

What makes you think that? Looking at the summary it looks like async is better in every way.

Better or equal in all the ways measured. But some things aren't measured, maybe because they're obvious to the author or because they're harder to quantify.

* Rust's async ecosystem [1] adds a lot of complexity over simple threaded code.

* Rust's async ecosystem doesn't interoperate as easily with C libraries written in a simple threaded way. (And it's debatable which interoperates more easily with C libraries written with a different event loop.)

* async tasks can't be preempted, so concurrency will fall off a cliff if they run on O(cpus) threads and involve long-running computations or accidental blocking.

I think it's reasonable to ask if these numbers are enough better to justify all that, particularly given the disappointing "this advantage goes away if the context switch is due to I/O readiness".

And to go back and argue pro-async for a moment, io_uring might eliminate that disappointing caveat.

Then again, on the pro-thread side, there's Google's interesting fibers model that might solve some of these performance issues. [2] Also, "~17µs for a new kernel thread" is the wrong number, since you can avoid that cost with a simple thread pool.

Personally I think some things are better written as async, but it's a mistake to impose it on everything. For example, if you're writing a web app in Rust, I think you're usually better off writing threaded request handlers and having a mechanism for them to interact with the async hyper code. The hyper code is better off as async because an Internet-facing server might have an enormous number of connections in keepalive state.

[1] or maybe I should say ecosystems, plural, given the current tokio vs async-std divide.

[2] https://lwn.net/Articles/826860/

Re: Comparison of Rust async and Linux thread context switch time and memory use

#39
post #9

Earlier quoted context omitted.

io_uring will help a lot here.

Well, it can, but not always. Remember that if you’re waiting for an event to arrive, that generally involves a syscall, the thread being put to sleep, and then the thread being woken up. Any time you’re doing that, think, “Could I just replace this polling system with a call to read()?” What io_uring does do is provide a way to poll without needing to wait, but if you haven’t received new events when you poll, you’r…

> What io_uring does do is provide a way to poll without needing to wait, but if you haven’t received new events when you poll, you’re not on the fast path any more.

Isn't "not on the fast path any more" a bit absolutist? io_uring's "slow" path is roughly one syscall per iteration, right? That's still many fewer syscalls than one syscall per IO operation (or more if any return EAGAIN/EWOULDBLOCK) as you'd be doing without it. I'm not sure I really care about eliminating that last syscall per iteration; it seems minor in comparison.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#40
post #20

Please don't mix nanoseconds and microseconds. This is just confusing to read. Stick to nanoseconds for everything.

ah, a fellow traveler - godspeed. what a sane, reasonable world we could have if nanosecond timestamps ruled supreme.
Post reply on HN