Live data from Hacker News

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

github.com

41–50 of 201 posts

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

#41

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 making one blocking syscall every time you would ordinarily make a blocking syscall.

I am a bit baffled how this could possibly be considered an “absolutist” viewpoint—I am just saying that there exist scenarios where io_uring is not helpful. This should be uncontroversial.

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

#42

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

I'd think mostly similar. Goroutines are "stackful" coroutines, though, so their memory use will be higher. They have an interesting stack copying model, so I'm not sure if they require as many pages as POSIX threads do. (Having a "denser" memory space and no guard page requirement would mean you could use huge pages and thus have much less TLB pressure.)

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

#43

Earlier quoted context omitted.

> 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 making one blocking syscall every time you would ordinarily make a blocking syscall. I am a bit baffled how this could possibly be considered an “absolutist” viewpoint—I am just saying that there exist scenarios where io_uring is not helpful. This should be uncontroversial.

You mean when there's only one thing to do per iteration? I'd describe that as when mostly idle. As the system gets more loaded, the one syscall per iteration matters less and less.

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

#44
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.

Sorry, what does tokio have to do with "async"? The default implementation uses posix threads, no?

It's a runtime for running lightweight tasks (`Future`s, async functions) on top of it. What is not async about it? And of course it still needs posix threads. The executor needs to run somewhere, and the only somewhere that an OS offers is a thread.

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

#45
post #8

I’m curious how this looks on ARM64. (Sorry, Aarch64.). x86 context switches are overcomplicated and inherently slow.

Well, here's what it looked like on my MacBook Pro with M1/16GB:

M1-MBP async-brigade % time cargo run --release 500 tasks, 10000 iterations:

mean 761.403µs per iteration, stddev 8.929µs (1.522µs per task per iter)

cargo run --release 3.21s user 4.60s system 99% cpu 7.818 total

M1-MBP thread-brigade % time cargo run --release 500 tasks, 10000 iterations:

mean 787.149µs per iteration, stddev 67.289µs (1.574µs per task per iter)

cargo run --release 0.94s user 7.19s system 100% cpu 8.081 total

I ran it a few times and the numbers came up rather similar each time: async-brigade finished in 760.273µs-764.928µs while thread-brigade took 784.510µs-796.323µs.

As macOS doesn't have taskset, I can't easily set affinity. I tried to use the workaround documented elsewhere to use Xcode's Instruments to reduce the number of CPU cores but it would always re-enable itself at 8 cores, so that didn't work.

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

#46
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.

> So involving "async" is totally the wrong tool for the job.

Sadly with so many things having gone async-first (or only) it’s become difficult not to end up with an async runtime anyway, or not be forced to use an async system. I wanted to build a small web-based tool for local, didn’t really find anything which was not async.

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

#47
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.

> So involving "async" is totally the wrong tool for the job. Sadly with so many things having gone async-first (or only) it’s become difficult not to end up with an async runtime anyway, or not be forced to use an async system. I wanted to build a small web-based tool for local, didn’t really find anything which was not async.

You might like Zig's attitude towards this question. Async/sync decision is a single compile-time decision there. The jury's still out whether that's a good idea though.

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

#48
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?

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

#50
post #16
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…

Linux is likely many years from having anything approaching a fully asynchronous system call interface, if anyone was willing to work on it (io_uring makes a huge dent but I don't think it's intending to reimplement everything). Even where async kernel interfaces exist, without reworking of the kernel-internal implementation still there is often the need for a thread for the kernel side to execute on. For example IIR…

> io_uring makes a huge dent but I don't think it's intending to reimplement everything

At this point it's proponents are being pretty unapologic that it will, in fact, reimplement every part of the syscall interface that is actively used.

Post reply on HN