Live data from Hacker News

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

github.com

61–70 of 201 posts

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

#61

How does Rust async compare to Goroutine, Erlang threads, Javascript async, Java async in performance and memory usage? Is there any benchmarks for that?

There were benchmarks and a discussion on this on reddit recently comparing goroutines to tokio. If I recall correctly tokio was slower than goroutines but if you set the right settings it could be almost as fast. https://www.reddit.com/r/rust/comments/lg0a7b/benchmarking_t...

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

#62
post #35

Earlier quoted context omitted.

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.

Is there a different benchmark that demonstrates a scenario where synchronous syscalls are better suited?

I doubt you'll find anything as comprehensive and well-presented as the TechEmpower benchmarks, because their particular scenario is one that a lot of frameworks care about competing on (partly because it's difficult enough to be interesting). But I'd expect any benchmark for batch-style processing of large volumes of data would show that.

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

#63
post #35

Earlier quoted context omitted.

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.

Is there a different benchmark that demonstrates a scenario where synchronous syscalls are better suited?

Presumably the difference would be smaller or for some frameworks even negative if each request did some actual and not entirely predictable amount of CPU work (e.g. executing some html templating scenario with varying levels of output and perhaps compression), and just in general much more work and using more memory (so the memory overhead is proportionally less relevant), and if the benchmark implementations were not permitted to tune exactly for the workload and system (i.e. so that generalized scheduler defaults are used on both kernel and userspace side). I.e., in a more real-world scenario with all the normal complexities and inefficiencies and development time constraints that are usual.

But yeah, it's be super interesting to actually see that demonstrated - that'd be quite a lot of work, however.

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

#64
post #35

Earlier quoted context omitted.

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.

Is there a different benchmark that demonstrates a scenario where synchronous syscalls are better suited?

If your request are huge. For example, imagine you need to read many huge files into memory.

Whether you read one file after the other sequentially, or try to read all of them concurrently, won't make a difference, because your Disk/RAM bandwidth is going to be bottlenecked anyways.

Trying to do this concurrently requires more work that won't pay off, so it might actually be slower.

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

#65
post #5

That's a huge help. I only need about 20 threads in Rust, some of which are compute-bound. So involving "async" is totally the wrong tool for the job. Goodbye, Tokio.

The only reason you wanted to use “async” was because of micro-optimization on thread context switches?

That's the most commonly given reason for using async/await so a lot of people assume threads are way more heavyweight than they actually are.

What other reason were you thinking of?

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

#66

Earlier quoted context omitted.

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 wi…

> io_uring's "slow" path is roughly one syscall per iteration, right?

Right, so if a blocking API makes 1 syscall, io_uring would make N syscals for N iterations.

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

#67
post #51

A meaningless comparison. Linux, being a preemptively multitasking OS, switches thread contexts regardless of what you're running. So the Rust async context switch is on top of the regular Linux context switch, not instead .

When you're in sub microsecond time scales, preemption events are relatively rare.

I don't see your point.

"Linux thread context switch time" is a meaningless metric, since Linux will switch thread context regardless of what you choose to run on your computer.

Any "async" switches are additional overhead; you don't get to not have kernel preemption just because your Rust thread is now switching contexts "asyncly".

There are benefits to having an additional user-mode scheduling mechanism inside your kernel thread, but saving CPU cycles isn't one of them.

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

#68

Earlier quoted context omitted.

The only reason you wanted to use “async” was because of micro-optimization on thread context switches?

That's the most commonly given reason for using async/await so a lot of people assume threads are way more heavyweight than they actually are. What other reason were you thinking of?

Ease of understanding multithreaded code and wait on results or perform standard control flow constructs in a multithreaded environment?

This is a great example in Node on useful combinators that with async await make it easy to express parallel programming concepts with familiar tools. No manual IPC, no fork/join child PID/thread ID handling, etc.

https://github.com/sindresorhus/promise-fun

The same abstractions (or many of them) exist in Rust, but I think the above is illustrative of the ways we can combine async object returning functions and then use await to hide the complexity of the state machines needed to drive them.

That this abstraction that makes code easy to read and write also performs better is the icing on the cake. The former prevents bugs and keeps code quality high, and that is worth much more.

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

#70
post #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 br…

Yes, the point of "async" isn't to save CPU cycles, it's to customize the scheduler so that you can prioritize resource use properly.

(E.g., don't switch to the systemd or sshd thread if a customer's web request is timing out.)

That said, doing this right is out of reach of the average programmer, and it's doubtful that the compiler has enough domain-specific knowledge to do this automatically.

"Async" of the Python and node.js fame is yet another thing, a hack to get around their interpreters' inability to use kernel multitasking features because of global locks.

Post reply on HN