Live data from Hacker News

Maybe Rust isn’t a good tool for massively concurrent, userspace software

bitbashing.io

81–90 of 624 posts

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#81
post #11

Earlier quoted context omitted.

> The lifetime of an Arc isn’t unknowable, it’s determined by where and how you hold it. In the same sense that the lifetime of an object in a GC'd system has a lower bound of, "as long as it's referenced", sure. But that's nearly the opposite of what the borrow checker tries to do by statically bounding objects, at compile time. > maybe the disconnect in this article is that the author is coming at Rust and trying t…

Honestly, the biggest stumbling block for rust and async is the notion of memory pinning. Rust will do a lot of invisible memory relocations under the covers. Which can work great in single threaded contexts. However, once you start talking about threading those invisible memory moves are a hazard. The moment shared memory comes into play everything just gets a whole lot harder with the rust async story. Contrast tha…

Interestingly, the newest Java memory feature (Panama FFI/M) actually can catch you if threads race on a memory allocation. They have done a lot of rather complex and little appreciated work to make this work in a very efficient way.

The new api lets you allocate "memory segments", which are byte arrays/C style structs. Such segments can be passed to native code easily or just used directly, deallocated with or without GC, bounds errors are blocked, use-after-free bugs are blocked, and segments can also be confined to a thread so races are also blocked (all at runtime though).

Unfortunately it only becomes available as a finalized non-preview API in Java 22, which is the release after the next one. In Java 21 it's available but behind a flag.

https://openjdk.org/jeps/8310626

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#82

I find myself in this weird corner when it comes to async rust. The guy's got a point in that doing a bunch of Arc, RwLock, and general sharing of state is going to get messy. Especially once you are sprinkling 'static all over the place, it infects everything, much like colored functions. I did this whole thing once back when I was starting off where I would Arc stuff, and try to be smart about borrow lifetimes. Tot…

> But then rust also has channels. When you read about it, it talks about "messages", which to me means little objects. Like a few bytes little. As a wise programmer once said, "Do not communicate by sharing memory; instead, share memory by communicating"

Ooh, that's very ezn. Ah crap I think I have a race condition.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#83
One of these days I really want to sit down and read a bunch of the takes on different async approaches by the Rust designers, and ponder the design choices in depth. Until then, I will defer judgement. I will say I prefer green threads as a user, but the common argument is that it is not a zero cost abstraction, and thus not appropriate for Rust, makes sense to me atm. I do wish async would get rid of its rough edges though (lack of async drop, async traits, scoped task, etc.) and at least become a first class citizen.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#84
post #52

Earlier quoted context omitted.

The context switch for threads remains very expensive. You have 4,000 threads but that's lots of different processes spinning up their own threads. it's still more efficient to have one thread per core for a single computational problem, or at most one per CPU thread (often 2 threads per core now). You can test this by using something like rayon or GNU parallel using more threads than you have cores. It won't go fast…

Since that time, context switching changed from a O(log(n)) operation to an O(1) one. I have no doubt that having a thread per core and managing the data with only non-blocking operations is much faster. But I'm pretty current machines can manage a thousand or so threads locked almost the entire time just fine.

> Since that time, context switching changed from a O(log(n)) operation to an O(1) one.

I'm not sure how that's relevant here, if for example something takes 1ms and I do it 1000 times a second, I'm using 1000 ms of CPU time vs not doing it at all. So if you want to use big o notation in this context it should be O(n) where n is the number of context switches, because you are not comparing algorithms used to switch between threads but you are comparing doing context switch or not doing it at all.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#85

Anyone with electrical engineering know if pata vs. sata cables is a good analogy for async vs. sync? I know parallel ATA cables were all the rage. They had a higher theoretical throughput when compared with serial ATA cables but there was too much cross-talk involved to make it actually faster in the end so now we have serial ATA cables everywhere with much higher throughput than parallel ATA cables could ever achie…

PATA vs. SATA is a somewhat limited metaphor; PATA had a number of limitations such as the inability to hot swap hardware as well as using wide ribbon cables that made it largely obsolete. In contrast, both sync and async programming have reasonable applications; we're likely using both for the foreseeable future. The best EE analogy I can think of is using hyperthreading to execute multiple processes on a single core vs scheduling each thread to a separate core, but that's less a metaphor and more of a simplified model of what async vs sync is actually doing.

> Should we move back away from parallelism and focus on handling synchronous stuff faster instead?

Rust already has excellent handling of synchronous computation, given that it can meet/sometimes exceed equivalent performance in C. The problem is when you're I/O or network bound; you can either throw threads at the problem (and by extension throw memory at the problem for the thread stacks) or use async programming.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#86
post #51
post #36

Earlier quoted context omitted.

In the sense that green threads are easier, sure. But green threads were not and are not the right solution for Rust, so it's kind of beside the point. Async Rust is difficult, but it will eventually be possible to use Async Rust inside the Linux kernel, which is something you can't do with the Go approach.

I think they are referring to channels, which came with the tagline "share memory by communicating."

Rust has had channels since before Go was even publicly announced. Remember that Rust, like Go, was inspired by Pike's earlier language Limbo, which uses CSP. https://en.wikipedia.org/wiki/Limbo_(programming_language)

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#87
post #32
post #16

Earlier quoted context omitted.

Hoare Was Right. (But if you're only firing up a few tasks, why not just use threads? To get a nice wrapper around an I/O event loop?)

Exactly. People are too afraid of using threads these days for some perceived cargo-cult scalability reasons. My rule of thumb is just to use threads if the total number of threads per process won't exceed 1000. (This is assuming you are already switching to communicating using channels or similar abstraction.)

The performance overhead of threads is largely unrelated to how many you have. The thing being minimized with async code is the rate at which you switch between them, because those context switches are expensive. On modern systems there are many common cases where the CPU time required to do the work between a pair of potentially blocking calls is much less than the CPU time required to yield when a blocking call occurs. Consequently, most of your CPU time is spent yielding to another thread. In good async designs, almost no CPU time is spent yielding. Channels will help batch up communication but you still have to context switch to read those channels. This is where thread-per-core software architectures came from; they use channels but they never context switch.

Any software that does a lot of fine-grained concurrent I/O has this issue. Database engines have been fighting this for many years, since they can pervasively block both on I/O and locking for data model concurrency control.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#88
Rust is designed like this because it seeks to achieve zero-cost abstractions and safety.

Or in other words, the goal is that you can think in abstract what the natural optimal machine code would be for a program, and you can write a Rust program that, in principle, can compile to that machine code, with as little constraints as possible on what that machine code looks like.

Unlike C, that also has this property, Rust additionally seeks to guarantee that any code will satisfy a bunch of invariants (such as that a variable of a data type actually always holds a valid value of that data type) provided the unsafe code part satisfies a bunch of invariants.

If you use Go or Haskell, that's not possible.

For example, Go requires a GC (and thus requires to waste CPU cycles uselessly scanning memory), and Haskell requires to use memory to store thunks rather than the actual data and has limited mutation (meaning you waste CPU cycles uselessly handling lazy computations and copying data). Obviously neither of this are required for the vast majority of programs, so choosing such a language means your program is unfixably handicapped in term of efficiency, and has no chance to compile to the machine code that any reasonable programmer would conceive as the best solution to the problem.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#89

Yes, async is effectively a much harder version of Rust, and it's regrettable how it's been shoved down the throats of everyone, while only 1% of projects using it really need it. Hover, async is also amazing in these 1% of cases when it's useful. If you have a service that handles massive amounts of network calls at the core (think linkerd, nginx, etc.), or you want to have a massive amount of lightweight tasks in y…

The argument here is that Rust chose to implement coroutines the wrong way. It went the route of stackless coroutines that need async/await and colored functions. This creates all the friction the article laments over.

But it also praises Go for its implementation, which is also based on a coroutine of a different kind. Stackful coroutines, which do not have any of these problems.

Rust considered using those (and, at first, that was the project's direction). Ultimately, they went to the stackless operation model because stackfull coroutine requires a runtime that preempts coroutines (to do essentially what the kernel does with threads). This was deemed too expensive.

Most people forget, however, that almost no one is using runtime-free async Rust. Most people use Tokio, which is a runtime that does essentially everything the runtime they were trying to avoid building would have done.

So we are left in a situation where most people using async Rust have the worst of both worlds.

That being said, you can use async Rust without an async runtime (or rather, an extremely rudimentary one with extremely low overhead). People in the embedded world do. But they are few, and even they often are unconvinced by async Rust for their own reasons.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#90
post #33
post #24

Earlier quoted context omitted.

I really like the message passing paradigm. And languages like Erlang have shown that its an excellent choice... for distributed systems. But writing code like that is a very diffferent experience from, say, async JavaScript, which feels more like writing synchronous code with green threads (except you have to deal with function coloring as well). I believe people will try to write code in a way that is already famil…

Go also uses goroutines and channels to facilitate message passing, or as they describe it, "sharing memory by communicating." I imagine Rust to be a language far more similar to Go, in both use cases and functionality, than JS.

And in the end, almost everything ends up using Mutex, RWMutex, WaitGroup, Once, and some channels that exist only to ever be closed (like Context.Done), and only if you need to select around them.

It's great, but message passing it is not.

Post reply on HN