If anyone is looking for another language that also uses the actor model for concurrency and has very similar ways of handling it like D, then checkout Erlang. It's language lessons are clearly still relevant today. :)
Erlang does go way beyond concurrency into the domain of distributed and highly-reliable systems, and has more than lessons for us IMO - together with OTP, it’s still arguably one of the most effective and battle-tested environments for highly concurrent systems.
Like anything, it has its envelope and sweet spots, as well as its warts. Some people find the Erlang language syntax to be ugly; I quite like it and find it easier to comprehend in general than Elixir, which is much more verbose to my eyes (except for the very nice pipe operator!)
Yes, the semicolon / period thing in Erlang can be annoying, but not so much that it really gets in my way.
The tooling can be a real barrier to entry, and I’m glad that Elixir is bringing more people into the fold, although I can’t bring myself to really like Elixir for purely subjective reasons.
For example, one of my major complaints about Elixir is that it is literally Erlang with some syntax changes, rewritten libraries, and extensions like macros - but if you don’t grok Erlang thoroughly, you will get frustrated when you hit the bottom layer - like exceptions and error messages - and it’s full of Erlang data structures. I had the same reaction when working with non-Java languages targeting the JVM. They all feel a bit like bolt-on kits. At some point, you really need to understand the core.
Important to point out Elixir as well, which is touted as a "more modern" way to write code that runs on the Erlang VM (so you get the same concurrency features, etc.)
The _only_ downside I find of Elixir is that it looks too Ruby-ish to my taste. I'm still waiting for the day a C-like language that runs on BEAM becomes mainstream enough.
Would any sort of actor model language have the raw throughput for a AAA game? I get how this model is nice for the web, for example, but I'm wondering why it doesn't get used for high performance computing (that I know of).
There are languages, like Pony [1], that use actor model for the sort of high performance you are talking about. Also check out Anna paper, there is a description and argumentation on how and why they use actor model in C++ for high throughput.
I would also say that performance wise actor model is usually better, than low level shared memory multithreading, because it enforces locality-friendly contention-free architecture and fundamentally maps better to modern hardware.
For their AAA games cloud infrastructure it does! In fact, it is a backbone for some of Microsoft's Halo AAA game. Microsoft has this framework called Orleans which uses the Actor pattern. They have used it in various other project's too.
Now if you are wondering about raw throughput for graphics and physics stuff in AAA games, that I don't know. I believe that to be a completely different beast, with different requirements, which may or may not benefit from this paradigm.
The Erlang VM (BEAM) is unusual in that it, like the languages it hosts, is very opinionated.
It is designed for robustness, scalability, concurrency, distributed environments. And immutable data. So far as I know, you literally cannot implement a language with mutability on the VM.
So, raw performance will never be its thing.
In general, I think the actor model could achieve high performance, but perhaps only if messaging is syntactic instead of truly distributed with mailboxes, network transparency, etc.
As far as I know, Ada code using its types of actors (tasks and protected objects) can be made very fast, and more importantly for AAA games, very predictable. The default scheduling methods are good enough for many cases, but with the right restrictions, scheduling can even be statically determined and, in principle, a cyclic executive could be generated by the compiler with the same semantics as the original actor code. Unsure if this is actually done in practice, though.
NaughtyDog used a job system with fibers to parallelize their engine [1]. I suppose that's not exactly the same as an actor system since the fibers don't necessarily own all their state nor do they necessarily communicate using messages.
Even in something like Go which has this kind of concurrency in mind, performance critical code is often written with the more traditional "threads & locks" approach with the goal of using the ideal number of goroutines to maximize hardware use but no more than that.
Not saying that other concurrency models don't have the throughput for a AAA game, but when your goal is to get the most out of the hardware of one desktop/console you're going to have different priorities than a server environment.
Libraries aren't the same as native support as the latter case influences all uses of concurrency in a language while the former only affects things that use that library and may be incompatible with other concurrency libraries.
There are ports of Akka and Akka-like libraries for a number of languages. Actor frameworks are relatively easy to find for most languages. Another variation of a sort of an Actor framework that gets used for relatively large projects is the Orleans framework for .NET: http://dotnet.github.io/orleans/
I'd argue the most interesting problems are those that end up having some sort of shared state requirement. Clojure handles this nicely with language primitives like atoms and refs, which helps avoid the pitfalls of the locking paradigm found in Go and Java, for example. What does D do in this case, then?
There are the basic primitives found in c/any language, but there are also synchronized{} (or synchronized (my_mutex) {}) blocks that essentially automagically handle locking/unlocking and let you just mutate around willy-nilly across threads (in that block). There are also atomics, although the syntax for using them is a bit clumsy (foo.atomicOp!"+="(7), anyone?).
Simple to fault, in that channels and Goroutines don’t prevent you from making mistakes the way actors do - ie you can still share state and memory. Go allows for the possibility of writing cleanly concurrent code, but you must study and practice.
Using an actor system, though, involves a bit more study with regards to setting things up, but it’s almost impossible to make mistakes - actors simply can’t access each other’s state (maybe some systems allow it, but you’ll have to struggle against the language to make that mistake).
I don't know about D, but Go channels and Erlang processes are sort of complements or inverses of each other in some sense.
A Channel in Go is a first-class communications bus that can be passed around as a value, and senders and receivers are implicit/not first class. Arbitrary numbers of readers and writers can use one channel. Channels are also typed; only specific messages can pass across a given channel, though it can be specified by interface.
In Erlang, you have to send a message to a specific process. Thus, the receivers (and symmetrically, the senders) are first-class objects that can be passed around, but the bus is implicit in the language. Processes may also receive any message, and should be able to deal with them. (One failure case that can occur in Erlang is a memory leak because some process is getting messages that it never receives, so they just build up in the mailbox. In practice this only happened to me maybe twice over the five years I was using Erlang, so it's not a stopper, just "something to be aware of", especially while debugging leaks.)
A positive for the Go model is that it is really easy to set up multiple readers for one writer, a common pattern, which Erlang handles somewhat gracelessly. (Yes, I am aware of the "pool" abstractions, all of which last I knew were one variation or another on "send a message to the pool coordinator to find out which process to send a message to", creating a single-process bottleneck on the pool.) There are some other nice ways to set up channel networks in Go to do some things Erlang would only be able to do with a lot more indirection and performance penalty on top of the fact that Erlang is already substantially (albeit not necessarily fatally) slower than Go. A negative for the Go model is that the way they've specified channels means that they rigidly must run in the same OS process; there is not and can not be a "network channel" in Go with the same semantics as a Go channel, because a Go channel is an "exactly once" abstraction, which is impossible to run over a network [1]. Also, on the off chance you want to "guarantee" that a given recipient will process a message, it's on you to guarantee that the channel does not "get around" to goroutines you didn't expect.
A positive for the Erlang model is that you get that sweet, sweet network transparency that makes writing Erlang-based clustered servers sweeter than any other language I know, because they defined the characteristics of their bus from the very earliest days of the language for that use case, in contrast to Go which wrote their fundamental abstraction in a way that network transparency is impossible. (Bear in mind that systems ought to be designed for that early, it is not automatic, but it is still a staggering advantage for the language.) The downside is that when you want to do anything other than have one process send a message to a specified other process, you're going to have some sort of indirection or bad API or bottleneck process or something like that. Depending on the nature of your server this price may range from utterly irrelevant to quite expensive, although I'd expect it to be your "biggest problem" quite rarely.
(I'm also only comparing the channels vs. PID-based message passing. There are other relevant issues like the shared memory in Go vs. enforced isolation in Erlang, etc.
[1]: And Go channels are "truly" exactly-once, too, so even Kafka's somewhat dodgy twisting of the term "exactly once" wouldn't be sufficient to implement them. Channels are used for memory synchronization, so it must be guaranteed that a non-buffered channel has had its message arrive on the other end because the fact the program counter of the receiver has advanced to that point in their code is something the language critically depends on, and a mere promise that it'll get there eventually, maybe twice, someday breaks that completely.
It's conceptually much simpler. Erlang(/Elixir) does not have separate concepts of routines and channels, you just send messages directly to a process (and it does whatever it wants with that), and not only does it embrace "don't communicate by sharing memory, share memory by communicating" it enforces it: Erlang's data types are mostly immutable and each process has its own private heap.
The language does look and feel somewhat odd though, it was inspired by prolog (so will look very odd if you've not used prolog) but doesn't do full unification (so will feel very odd if you've used prolog).
I'm a big fan of Erlang—and believe it's a useful language to know even if the daily language is something else—but my understanding of D is that it excels in a different problem space (build efficient native code, etc.)
defined|8 years ago
Like anything, it has its envelope and sweet spots, as well as its warts. Some people find the Erlang language syntax to be ugly; I quite like it and find it easier to comprehend in general than Elixir, which is much more verbose to my eyes (except for the very nice pipe operator!)
Yes, the semicolon / period thing in Erlang can be annoying, but not so much that it really gets in my way.
The tooling can be a real barrier to entry, and I’m glad that Elixir is bringing more people into the fold, although I can’t bring myself to really like Elixir for purely subjective reasons.
For example, one of my major complaints about Elixir is that it is literally Erlang with some syntax changes, rewritten libraries, and extensions like macros - but if you don’t grok Erlang thoroughly, you will get frustrated when you hit the bottom layer - like exceptions and error messages - and it’s full of Erlang data structures. I had the same reaction when working with non-Java languages targeting the JVM. They all feel a bit like bolt-on kits. At some point, you really need to understand the core.
In my opinion, of course.
elsurudo|8 years ago
strkek|8 years ago
pessimizer|8 years ago
Is it really touted as that? It's just a more Algol-looking way to write for Erlang semantics.
syeaj|8 years ago
zzzcpan|8 years ago
I would also say that performance wise actor model is usually better, than low level shared memory multithreading, because it enforces locality-friendly contention-free architecture and fundamentally maps better to modern hardware.
[1] https://www.ponylang.org/
[2] http://db.cs.berkeley.edu/jmh/papers/anna_ieee18.pdf
chamakits|8 years ago
Who is using Orleans: https://dotnet.github.io/orleans/Community/Who-Is-Using-Orle...
Video presentation on it: https://www.youtube.com/watch?v=7OVU9Mqqzgs
Now if you are wondering about raw throughput for graphics and physics stuff in AAA games, that I don't know. I believe that to be a completely different beast, with different requirements, which may or may not benefit from this paradigm.
macintux|8 years ago
It is designed for robustness, scalability, concurrency, distributed environments. And immutable data. So far as I know, you literally cannot implement a language with mutability on the VM.
So, raw performance will never be its thing.
In general, I think the actor model could achieve high performance, but perhaps only if messaging is syntactic instead of truly distributed with mailboxes, network transparency, etc.
kqr|8 years ago
mastax|8 years ago
[1]: https://www.gdcvault.com/play/1022186/Parallelizing-the-Naug...
jrs95|8 years ago
Not saying that other concurrency models don't have the throughput for a AAA game, but when your goal is to get the most out of the hardware of one desktop/console you're going to have different priorities than a server environment.
Thaxll|8 years ago
pjmlp|8 years ago
nkim12|8 years ago
eikenberry|8 years ago
pratikkonnur|8 years ago
WorldMaker|8 years ago
nickbauman|8 years ago
earenndil|8 years ago
Cthulhu_|8 years ago
sudhirj|8 years ago
Using an actor system, though, involves a bit more study with regards to setting things up, but it’s almost impossible to make mistakes - actors simply can’t access each other’s state (maybe some systems allow it, but you’ll have to struggle against the language to make that mistake).
jerf|8 years ago
A Channel in Go is a first-class communications bus that can be passed around as a value, and senders and receivers are implicit/not first class. Arbitrary numbers of readers and writers can use one channel. Channels are also typed; only specific messages can pass across a given channel, though it can be specified by interface.
In Erlang, you have to send a message to a specific process. Thus, the receivers (and symmetrically, the senders) are first-class objects that can be passed around, but the bus is implicit in the language. Processes may also receive any message, and should be able to deal with them. (One failure case that can occur in Erlang is a memory leak because some process is getting messages that it never receives, so they just build up in the mailbox. In practice this only happened to me maybe twice over the five years I was using Erlang, so it's not a stopper, just "something to be aware of", especially while debugging leaks.)
A positive for the Go model is that it is really easy to set up multiple readers for one writer, a common pattern, which Erlang handles somewhat gracelessly. (Yes, I am aware of the "pool" abstractions, all of which last I knew were one variation or another on "send a message to the pool coordinator to find out which process to send a message to", creating a single-process bottleneck on the pool.) There are some other nice ways to set up channel networks in Go to do some things Erlang would only be able to do with a lot more indirection and performance penalty on top of the fact that Erlang is already substantially (albeit not necessarily fatally) slower than Go. A negative for the Go model is that the way they've specified channels means that they rigidly must run in the same OS process; there is not and can not be a "network channel" in Go with the same semantics as a Go channel, because a Go channel is an "exactly once" abstraction, which is impossible to run over a network [1]. Also, on the off chance you want to "guarantee" that a given recipient will process a message, it's on you to guarantee that the channel does not "get around" to goroutines you didn't expect.
A positive for the Erlang model is that you get that sweet, sweet network transparency that makes writing Erlang-based clustered servers sweeter than any other language I know, because they defined the characteristics of their bus from the very earliest days of the language for that use case, in contrast to Go which wrote their fundamental abstraction in a way that network transparency is impossible. (Bear in mind that systems ought to be designed for that early, it is not automatic, but it is still a staggering advantage for the language.) The downside is that when you want to do anything other than have one process send a message to a specified other process, you're going to have some sort of indirection or bad API or bottleneck process or something like that. Depending on the nature of your server this price may range from utterly irrelevant to quite expensive, although I'd expect it to be your "biggest problem" quite rarely.
(I'm also only comparing the channels vs. PID-based message passing. There are other relevant issues like the shared memory in Go vs. enforced isolation in Erlang, etc.
[1]: And Go channels are "truly" exactly-once, too, so even Kafka's somewhat dodgy twisting of the term "exactly once" wouldn't be sufficient to implement them. Channels are used for memory synchronization, so it must be guaranteed that a non-buffered channel has had its message arrive on the other end because the fact the program counter of the receiver has advanced to that point in their code is something the language critically depends on, and a mere promise that it'll get there eventually, maybe twice, someday breaks that completely.
0xdeadbeefbabe|8 years ago
https://cstheory.stackexchange.com/questions/184/whats-the-d...
https://en.wikipedia.org/wiki/Actor_model_and_process_calcul...
Thaxll|8 years ago
masklinn|8 years ago
The language does look and feel somewhat odd though, it was inspired by prolog (so will look very odd if you've not used prolog) but doesn't do full unification (so will feel very odd if you've used prolog).
yahyaheee|8 years ago
athenot|8 years ago