24 days of Rust – Rayon
siciarz.net
siciarz.net
Being able to switch .iter() to .par_iter() and have things "just work" is a game changer.
The crucial thing about rayon is that sequential fallback is really fast, almost as fast as the sequential code you'd write anyway. This is important because, as paradoxical as it sounds, most CPU-bound programs work with small workloads most of the time, and so they don't want the overhead of parallelism for those cases. (It's the analogue of saving power by putting the CPU to sleep when it's not in use.) The occasional big workload that comes along is what you really want parallelism for, and the big trick is to handle that case without regressing the common sequential case. Rayon's work stealing approach based around scoped iterators is the ideal solution for this.
It's called .parallel() in D, works the same way I guess. It turns a lazy computation chain into a parallel one.
You shouldn't use rayon for blocking I/O; that's not what it's designed for. Rayon is a parallelism library, not a concurrency library.
Whenever people say this, I ask the following question in order to gauge whether I want to try the library in question:
Have you used Twisted?
Twisted is, to me, the quintessential example of a high-quality open source project. If you have used it extensively and still recommend Rayon, I'll give it a try.
Is there a good guide for someone in my position? ie, to learn about tokio and rayon, having used python (for, in this case, concurrency and data science respectively)?
Are you mostly using Rust these days?
I'm not sure there's a great guide yet, because a lot of this stuff is still shaking out. The Rust ecosystem in general is growing at a pretty steady clip, and new stuff pops up all the time: tokio is less than a year old, for example.
There's two different kinds of problems here: "I found a library, what does it do?" and "What libraries exist?" In the former case, you're at the mercy of the library author to give you a good description. With the latter, one of the better ways is to drop by #rust on IRC, or post to users.rust-lang.org, asking for an overview of what exists. https://crates.io/search is also helpful.
In this case, rayon is for "data parallelism", meaning "I have some data, I would like to do some work on it, and I'd like to make that paralell." Tokio is about asynchronous I/O.
Granted, it definitely took some time for me to gain the experience necessary for this magic to occur. It's not really a "hack a quick thing together once every 10 years" kind of language.
It's hard to convey this without getting you to actually learn Rust for a few days/weeks/months, but it's certainly been my experience and I hear it all the time from other Rust developers.
As an extremely simple example of this, compare BASIC and APL in terms of the learning curve. If we examine it in isolation, BASIC is obviously better. But if we use multiple criteria, the answer becomes much more nuanced, as we can see what the steeper learning curve allows. For a less extreme, but still ultimately the same comparison, imagine Perl and Python, or Go and Rust. A simple mental model is important, but if one choice is less simple, the question should really be what are you getting in return, and is the trade-off worth it? Otherwise, you should just program in BASIC and be done it it.
Several times now when caught out by the borrow checker, I've wondered, wow, how do other languages get away with allowing a situation like this.
This video series by the author of Rayon (and Rust core dev) was what first hooked me and made the syntax a lot more understandable: http://intorust.com/
Compared to Python: Rust is certainly a bit more verbose, due to being much more careful with performance, memory and precise types. But I find that the last one actually makes Rust easier to maintain. In Rust, I just have to look at the types being passes around, while in a complex Python code base, I might ask PyCharm for all calls to a function and work backwards, slowly try to infer what kind of thing a variable might be.
Go is much closer to Rust in terms of maintainability, but I haven't worked with it enough to say, because I didn't like other aspects of the language.
Scala tries to enable you to create whatever api you want, so it has many different flavours of magic to allow flexibility of expression. Predictably this is used and abused horribly by a community who can't come to a consensus on what good taste is.
Rust on the other hand makes you explicitly say everything that happens. Nothing will happen without you being aware of it, and that makes it wordy and feel more complex, but it's the kind of complexity of having to say everything you mean down to a much greater level of detail rather than the kind of complexity that arises when it's almost impossible to know exactly what is going on.
Rust and Scala are pretty much at opposite ends of the 'magic' spectrum.
This is exactly the opposite what I understood from the article that Rayon does. For example, it will schedule your code differently depending on the current conditions of the CPU. That kind of non-determinism is hard to deal with if you are doing something more complex than adding numbers. I imagine this is fine for building mathematical libraries, where you care about the result, not about the order of operations. But if you are interacting with external APIs, it's very hard to reason about this kind of parallelism.
The pitfalls of parallel and concurrent computing are well known, and you are correct should not use a data parallelism library for effectful actions whose order matters. But of course the same concerns can arise when using goroutines.
In contrast, Rust actually does guarantee an absence of data races, in rayon or in any other concurrency or parallelism library, whereas Go provides no such guarantee.
Rayon requires the callbacks it calls to be `Sync`. That means that they are declared to be safe to run across threads. That means that the API it provides explicitly requires the calculations to be parallelizable (and this is compiler checked), and if it doesn't run them in parallel, that can't hurt, right?
You are aware of it. You put the par_iter() call there. Rust won't magically parallelize regular .iter() loops.
If you don't want to reason about parallelism, don't use Rayon. That's pretty explicit.
I realize that Go's creators intended for it to be a systems language. In practice though, it has found its niche in web apps or microservices, and command-line apps in the DevOps world. Rust is primarily aimed toward real-time applications that can't tolerate garbage collection latency.
Of course they're both general-purpose languages in theory. But in the real world, one is really competing with Java and Python while the other is competing with C++. I don't even see Go and Rust as head-to-head competitors at all... and I definitely don't understand their uni-directional "feud" (i.e. nearly every Rust thread is has people taking shots at Go, yet most Go threads don't mention Rust at all).
On the other hand, Go is aware of its limitations, and thus has no need to fire any shots back, so to speak.
However, the standard library and surrounding ecosystem for all the other players "fell into place" years ago. Java and Python have robust and well-maintained (sorry Node!) libraries and drivers for everything you could imagine. The biggest draw for Go is probably that its standard library is so complete, you seldom need many outside dependencies at all. Other rising stars such as Elixir are basically web-first from the start, rather than hoping to grow into it later.
From what little tinkering I've done with Rust... it seems to have a much steeper learning curve than other languages, and an ecosystem with few database drivers and only a couple of half-baked web frameworks (http://www.arewewebyet.org). I don't mean that disrespectfully, since it clearly has generated a lot of excitement in other niches.
However, I personally don't really care about those other niches. We web folks are a much larger community, and for the most part we don't really care whether or not a language uses garbage collection (web apps are more likely to be I/O-bound rather than CPU-bound). So while I would love for Rust to become another serious option, it's basically optimized in the wrong direction for the web mass market... and lags years behind in the ecosystem support that they care more about.
Why can't Firefox tolerate garbage collection latency?
I don't know about microservices but I think it could compete for command-line devops apps.
Often resulting from "this is so much more complicated than Go, why bother?" prodding as seen in lukaslalinsky's post above, at which point those "shots" are just asked-for information.
Martin Odersky, the creator of Scala, has said as much. Just look at Kotlin, Swift, and to some degree OCaml (upcoming modular implicits) and C# 7. Even Java 9 adopts Scala syntax with underscore now reserved.
Rust is its own thing, however. OP is probably referring to complexity of syntax that more powerful languages present to new comers.
To me, Rust feels like the simplest possible language that could achieve their design goals, whereas Go feels like they went so far out of their way to make it simple that they lost track of the other goals altogether.
I got myself out of the position of contrasting it against whatever they were railing against (in this case Erlang), and instead just put the question back on them. "I don't know, you tell me why I should consider Go for this."
Usually shuts down the conversation because they don't actually know anything about Go. Just that they've heard other people talk about it.
I find it really interesting when people say this, because to me, Go only seems like a suitable replacement for higher-level C++, whereas Rust could conceivably be used in any domain where C++ would be suitable. I'm not sure if this is the general consensus or just my perception though, so this could be clouded by my bias of being a heavy Rust user.
I think this is seriously over-stating it. We don't want Rust to be ugly or hard to use.
What got me thinking about that was the implementation of into_par_iter, which is a trait implemented on a bunch of standard types, not unlike implicit methods in Scala. I know this is rare code, but reading this left with very similar feeling that I had when reading Scala standard library code:
https://github.com/nikomatsakis/rayon/blob/master/src/par_it...
Can you describe the simpler signature of par_iter() that would work in Go?
Can you describe the Go features that allow the compiler to prevent data races at compile time?
I know it's a low-ish level language that leaves a lot explicit for the developer to type, and that a lot of boilerplate things could yet be turned into conventions or macros much like try!, as the language matures.
I'd really like to see this process accelerate as a lot of Rust code now is littered with into(), unwrap() etc. which causes mental overhead for the reader.
I'd like to see more "zero cost abstractions" where the zero also includes zero characters typed that aren't part of the problem itself. As a benchmark, I'd like to see "noise parity" with go/ocaml/swift while still having the nice safety/performance Rust has today.