Concurrency I can finally understand and write correctly
jordi.inversethought.com
jordi.inversethought.com
java.util.concurrent is probably the single best concurrency library I have ever used. Bar none.
When I'm writing in Java, and I find myself writing a lock, my first reaction now is: "This is okay for exploration, but which data structure from java.util.concurrent am I going to have swap in here when it's all done. And can I do it now and save myself a lot of grief."
It only took me 7+ years to come to this realization. Apparently I'm a slow learner. :(
some thread corrupted this date
That just doesn't work for most concurrent code. Often you have dependencies between data structures or other operations intervening. Most of the time it isn't a single structure, but a group of semantic operations across the program.
Recent example: I am using Tomcat as a servlet engine. For dependency injection I am using JNDI. Tomcat can be configured to call a provided ObjectFactory to generate JNDI objects when they are requested. The environment I am using allows for some kind of blue-green deployment, I can switch between different fired up virtual machines transparently to users. I use this when upgrading. My provided Objectfactory logs when objects are created (some of them should be strictly singletons). Sometimes the logs indicated that multiple objects where created after switching to the newly updated machine. This was due to a high load on the system in which multiple threads where busy with incoming requests simultaneously. Neither the language library nor Tomcat has any safeguards in place (synchronization of any sort) although JNDI and in particular Tomcat supports singleton creation. I had to resort to statically creating the required instances at loading the Objectfactory, a pattern I dislike with a passion (you can't swap the implementation during runtime which can be annyoing in any long-running environment).
also:
(multi-threading + shared-nothing) = much less complexity = more correctness
Not OP, but, if that does not give you more speed I'm not sure your problem should be multithreaded in the first place. Threads themselves are not free, there is a non-trivial cost in switching thread context. Trying to use multiple threads to wiggle the same memory area is insanity in most cases however you look it.
We've got an app that is pretty slow. Partially the fault of the front-end doing a lot of synchronous promises.
But on the backend we have a lot of calls where we are getting a list off foos from the DB, then spinning through for loops and making one or two synchronous calls to other microseconds to fill in the details.
I hated algorithms - but this is O(n^2) and we could cut it down to 2n -> O(n) by spinning off the fir loop to futures.
This is java using completable futures with whatever thread pool the generic executor creates.
Any issues?
Ofc, in Rust you can safely send mutable messages and transfer ownership.
Still chewing on this, but it would immediately strike me that relying on this behaviour in a software application, for it to be performant, would be a bad idea.
Also, immutability helps people reason when the language doesn't have a clock. If the language does have a clock -- i.e. it is synchronous, like Eve or Céu -- mutability poses no difficulty.
And I think you also have a very narrow view of multi-threading. Shared memory, mutable-everything isn’t the only way of doing multi-threading (or more precisely concurrency). This very article is trying to introduce to you a new way of doing concurrency that doesn’t involve mutable variables shared between threads.
(Of course there are good reasons for using immutable values; performance is just not one of them.)
Swift collections are logically immutable but may mutate under the hood if there is a unique owner. This conceptually provides a best-of-both-worlds, but a downside is that the language does not provide much help to enforce single-owner (yet).
You can pass an object around between threads with no issues, as long as only one of them owns it at a time. This is generally the model that is used in practice (although often in functional languages it's hidden under some CoW semantics).
However, I think you can get pretty far these days with just the built-in Java class library, using features like Futures (CompletableFuture), ExecutorService, ForkJoinPool, and BlockingQueue and such. Before CompletableFuture there was Guava's ListenableFuture. Synchronized methods are also useful in certain situations. I've built several fairly sophisticated concurrent applications using these primitives and felt they worked out reasonably well. I find that BlockingQueues with thread-pool-backed ExecutorServices provide a model that's fairly robust while being simple to reason about and monitor.
It's not just about safety, it's also about frequently not even needing it. I've achieved some pretty tidy code simplifications without harming the performance of the overall application by removing unnecessary multithreading. I've even sometimes achieved some pretty tidy performance increases while simultaneously simplifying the code by removing unnecessary multithreading.
It also depends on your environment. That 2nd app I mentioned was running on physical hardware that was running many applications. In that kind of environment, you can end up in a sort of, "double your CPU cores, double your cache misses" situation. And the performance story ends up not just being about one little module; it's about the entire system. There can be a sort of performance prisoner's dilemma, where trying to individually maximize the performance of every single piece in isolation actually results in slower overall performance.
When you're surrounded by people who (with good intentions) advocate fiddling with things, there's a lot of value in also having someone to argue for a more moderate approach.
It used to be that the excuse was bug fixes don’t sell. Maybe not, but buggy software does lose customers. They may bring in the newbies with features, but you retain with bug fixes.
Bug fixes do sell when paired with minor development, major impact features. Push the load to a couple of built-in apps to make them more smooth and focus on under-the-hood stuff.
The press will praise that as the best thing since sliced bread - a modern, polished and stable OS release.
Of course this would be a one-off thing and we'll get another major release with features, features, features!
I have worked with Java, C# and Python, and for each I have developed a concurrent object framework, making it quite easy to work with multible channels of incoming events, for example for process control systems with barcode-scanners, scales, I/O-busses etc. connected to the system
I think ponylang does a pretty good implementation of the Actor model.
Feel free to have a look at the Python impl. of the framework (www.github.com/pylots/pyworks)
@Scheduled(fixedRate = 5000) public void poll() { getCrapAndPutOnConsumerQueue(); }
I think that the data structures used by D for message passing are the real interesting part here. Akin to not using raw pointers, don't use raw data structures to convey state updates and let the owner of that state do it themselves with a queue or some abstraction for messages.
As I said, the whole thing was very enjoyable. I had lots of fun using D.
:-)
nice post
Especially the .hyper method that turns a regular sequence into hyper sequence that gets processed in parallel. First 5000 prime numbers:
# single-thread, takes 14.893s
put ^∞ .grep(*.is-prime).head: 5000
# multi-threaded, takes 8.853s on my 2-core box
put ^∞ .hyper.grep(*.is-prime).head: 5000For parallelism as what you describe, D instead has approximately the same thing. There's a `parallel` function in the standard library that works on any range (i.e. any iterable). You basically instead of doing
foreach(object; somerange) {
// ..
}
do foreach(object; parallel(somerange)) {
// ...
}
and it works like your Perl example.If you want it more functionalish, without writing out the loop, you can use `taskPool.map` instead of ordinary `map` for the same effect. More details:
stdlib: https://dlang.org/phobos/std_parallelism.html
Programming in D chapter on parallelism: http://ddili.org/ders/d.en/parallelism.html
Even an empty Rakudo Perl 6 program is a multi-threaded program, as the dynamic optimizer runs on a separate thread. I'd figure if that's possible, concurrency/parallelism would be quite a first-class citizen, not a best hope on top of something.
Initially the actor model was designed in the 70s.
I have been using guile fibers for some time, and it is a marvel! Not only can I write most code as if it was single threaded, but I can mix fibers code with pthreads and use fibers message passing when I need it.
> The _only_ downside I find of Elixir is that it looks too Ruby-ish to my taste.
Yeah, preach it!
> I'm still waiting for the day a C-like language that runs on BEAM becomes mainstream enough.
Eww.
I wouldn't mind a Lisp syntax too, but Ruby and ML are definitely a "no" from me. I just can't bring myself to like them. Their syntax feels too "loose", not sure if that makes sense.
Btw, I know there are some Lisp-like implementations out there, but same criteria applies: still not mainstream enough as of now.
- `elsif`
- `end`
- `unless`
- `a unless b`
- `f 1` vs `f(1)`
- Implicit method calls (`f` vs `self.f`). I know both have different behavior, I just don't like this "implicit self".
... etc.
I find Python okay-ish though, as paradoxical and nonsensical as that might sound. But in my toolchain Python has been reduced to little more than a calculator, since it's been replaced by Go for most of my automation needs.
Btw, friendly reminder that this is my subjective opinion regarding my own tastes, not absolute truth on aesthetics.
EDIT: Formatting.
I rarely find myself using if/else statements and I definitely avoid 'unless'.
One of the 'Rubyisms' I really disliked was the optional parentheses for method calls. While these are still optional for good reason, in practice this is no longer the case. The formatter will add parentheses and IIRC the compiler will give you a stern lecture too.
Obviously taste is subjective, but I do agree it matters.
I'd say if the Ruby-like syntax is what keeps you from trying Elixir, remember that's it's only skin deep and at least for me, the bad stuff is not really there compared to Ruby.
Furthermore, the advantages of having a functional and 'lispy'/homoiconic language that doesn't look like my bathroom floor after clipping my toenails is worth any remaining 'niggles' like the begin/end thing, or optional [] in the last parameter of a function call, etc. (which just like the optional parentheses is there for a very good reason).
> The _only_ downside I find of Elixir is that it looks too Ruby-ish to my taste.
Granted. More ML-like would be nice.
> I'm still waiting for the day a C-like language that runs on BEAM becomes mainstream enough.
WTF!?
I used to have a similar desire!
I actually started working on a language that transpiled to Haskell, like CoffeeScript -> JS. What I found, however, is a lot of little ways to make it more and more terse, until I ended up with something resembling Haskell’s syntax.
After this happened several times, I had some new found appreciation for Haskell’s syntax. It’s god awful for someone coming from a C-like background, but that seems like a very temporary problem.
I went from hating Haskell’s syntax so much I wanted a language wrapping it, to realizing it’s actually rather well thought out and other languages have a lot of noise to them. Lots of curly braces and semicolons that seem to mostly be there to make writing a compiler easier.
I still love C-like syntax, and still don’t like Haskell’s syntax, but not enough to write a CoffeeScript-like language.
# events per day
ndbh.fetch('select ts from tbl').all.map { |row|
row[:ts]
}.map { |t|
t.strftime('%Y-%m-%d')
}.each { |day|
days[day] += 1
}
(yes this could be conciser)This is generally frowned on, and I'm sure everyone who likes having this style
foo
.map {}
.select {}
.blah {}
will frown as well, but it's so much more visually appealing to me, coming from a C/perl background when I first encountered Ruby (many years ago).Funny how important syntax is. So easy to look at code in another style/language and go THATS NOT RIGHT!!
I'm waaay too picky in this aspect. I once was reading about Ada (before my bias towards C-like languages kicked in). It looked interesting and all, and I was like "Hmm, not bad, learning this might be interesti--"
> Put_Line
Nope.
The irony here is, my current language of choice is Go, and tests must be named like `TestType_Method` and I'm like "arrrgggghhh".
EDIT: Btw I don't really mind indentation styles, brace placement, "dot placement" (like your example) and those things, as long as they're consistent across the codebase.
Is it really touted as that? It's just a more Algol-looking way to write for Erlang semantics.
That said, I think Elixir has helped inspire the Erlang community to get better at documentation and tooling, so that's a definite win for both.
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).
without any shared data, and an asynchronous-message-passing style concurrency (i.e sharing memory by communicating, as opposed to communicating by sharing memory) can you please elucidate how we might get into deadlocks ? thanks !
If process A sends a synchronous message to B, and B while processing it sends a different synchronous message either directly to A or to another process that eventually calls back to A, you end up in a deadlocked state.
with synchronous messaging it is quite easy to see how deadlocks can happen, without trying too hard. for async messaging, which is what i was referring to, it is quite hard (and or convoluted) to get into that state...
loop(State) ->
receive
msg -> loop(State)
end.
Will block waiting for `msg` to show up. So if you have two processes that are interacting with eachother and aren't careful in constructing their communication you could end up with something (contrived): loop(Pid) ->
receive
msg -> Pid ! msg
end,
loop(Pid).
If you have two processes A and B that end up in this same loop but referencing each other, they'd deadlock. Substituting A and B for Pid you'd end up with something like: loop(B) -> % Process A
receive
msg -> B ! msg
end,
loop(B).
loop(A) -> % Process B
receive
msg -> A ! msg
end,
loop(A).
Both waiting for `msg` but never sending it to their partner process.but then you can 'deadlock' for a single pid as well right ? where you wait for a message which no one is sending...
i.e. a deadlock can only be determined when you look at the system as a whole from the outside and determine that it's permanently stuck.
yes, i know.
as i have pointed out elsewhere, with synchronous invocations, it is easy to get into a loop (or deadlock) i.e. pid-a -> pid-b -> pid-c -> pid-a. for the async case, which is what was elucidated by gp, it seems to me that the example is 'deadlocking' only because no one is sending messages expected by the other party...
And responding to your lower down comment: Yes, if a third party could send `msg` to either of these it'd break the deadlock. In my example (and the cases I've caused this myself) there was no other process around to do that. But this is where, with experience, you learn to design things better and also use `after` clauses in the receive expression. This will cause them to time out and do something. Which may be to terminate and let a supervisor restart the whole thing or some other behavior.
more importantly I've found that as you grow such Go programs, write higher-order actors, and deal with all the error/cleanup cases, the selects start to get really brittle and you end up spending alot of time refactoring them and carefully going over all the edge cases.
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).
https://cstheory.stackexchange.com/questions/184/whats-the-d...
https://en.wikipedia.org/wiki/Actor_model_and_process_calcul...
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 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.
Elixir allows reassignment, of course, in the end, it uses different "Erlang" variables, but the language abstracts it.
No. That can trivially be inferred from SSA being a thing, mutable bindings can trivially and automatically be converted to immutable ones.
Am I correct in thinking that this would be pretty okay for most games, AAA or not? Would an FPS be possible (quick updates, small messages)? Or a World Of Warcraft or Sea of Thieves style game with many actors that need sort-of-realtime performance but don't rely on it entirely?
I've been looking into creating a game, and I'm also learning Elixir, so I'm curious what would be realistic when combining both.
I assume in general they're very graphics intensive, which is (based on a 20+-year-old education I never finished) very matrix mathy, the type of calculations you absolutely would not want to shove through the Erlang VM.
However, one architecture people have used to varying degrees of success is using Erlang as a control layer (messaging, resilience) and C/something else compute-heavy for data manipulation. Erlang has the ability to drive external binaries via NIFs or ports.
Historically that's been a bit risky because the Erlang scheduler requires insight into its processes to do its job properly, but there have been improvements in recent releases.
So...maybe? Probably a question better suited to the Erlang users mailing list.
If we tune our sights down from "AAA game", there are two possibilities. One is you can create a less computationally-intensive game that can run Erlang on the desktop. I suspect you'll find you're a bit short of libraries for that use case, but with motivation you can pound through that. I'm not sure if this has ever been done. The other thing you can use Erlang for is being the backend server of a game system, and that is eminently practical, in the sense that it has been done: http://erlang.2086793.n4.nabble.com/Erlang-Who-uses-it-for-g... You'd encounter some bumps if you tried to scale it up, but that's not a very strong criticism since it's constant regardless of what tech you'd end up using.
No. Soft real time just means that the GC will not pause the process and and it will not cause stuttering during garbage collection pauses. In languages with GCs this is usually achieved by not allocating too many objects in your game loop.
If you write the 3D engine in erlang your game won't stutter but it will have an incredibly low framerate.
The secret ingredient to high performance is primarily data locality and obviously a compiler/runtime that can actually translate the program into efficient code. You can make your compiler as good as you want, without data locality and control over the memory layout the performance simply won't be good enough.
What do I mean by data locality? Primarily these things.
Avoid indirection through pointers.
Java's ArrayList is a nice example. You cannot store raw "int"s in an ArrayList. Only "Integer" objects. This means if you want to calculate the sum of the ArrayList the CPU will have to read the data from main memory if it is not in the CPU cache. This is roughly two orders of magnitude more expensive than an a single addition for example. If data can not be found in the cache it is called a "cache miss". Of course in python, javascript, erlang almost everything is a pointer that points.
Continguous storage of data.
Java still have primitive arrays. You can declare an int[] array to avoid the above problem. This means the integers are stored directly inside the array. Well what if the array is not in the cache? Wouldn't that incurr the same problem as a above?
A CPU is actually quite smart. It has a prefetching unit that can detect if you're loading data in a regular pattern and load the next piece of data according to that pattern.
If you're the CPU and see this pattern. What would you do to avoid a cache miss?
arr[0] arr[1] arr[2] arr[3] arr[x]
Of course you would start loading arr[4], arr[5], arr[6], arr[7] ahead of time!
Efficient storage
ArrayList stores a continguous list of pointers. On a 64bit architecture this means every pointer costs you 8 byte of memory. On top of that every object in Java has a "header". I don't know the exact number but let's say it's size is 8 bytes. Then there are alignment restrictions. Let's say each object is aligned by 8 bytes. 8+8+4 is 20. The nearest multiple of 8 is 24. We're storing a 4 byte number in 24 byte worth of memory. In other words if we are memory bandwidth constrained we can increase our performance by another 6x just by reducing the amount of "useless" data we have to read.
Mutation
There are efficient immutable datastructures but these usually involve indirection through pointers. Avoiding allocation of new memory avoids GC pauses or time spent in malloc/free. Mutating only a small part of a datastructure is more efficient than creating a copy.
Stack allocation
The primary benefit is that the top of the stack is usually in the L1 cache. Managing a stack is more efficient and a GC or manual memory allocation. It's just a pointer that gets incremented or decremented.
Erlang doesn't give you control over either of these. Ponylang has actors with isolated GC heaps and gives you control over memory layout and data locality.
If so, don't use Erlang.
The corrolary is "Am I mostly passing data around, doing I/O, etc?"
If yes, Erlang is probably a reasonable choice (and a GREAT one if you're doing a lot of concurrent stuff).
So games, backend server -can- work; it just depends what it's doing. You may need to mix and match for functionality if you're doing a mix of things, and then you have to ask if it's worth the effort and translation cost if you have to jump between languages.
You could do so, several different ways, e.g.:
(1) use the process dictionary,
(2) store data transparently to the new language’s user in ets (or, similarly, dets/mnesia),
(3) use separate (again, hidden from the language user) Erlang processes for mutable cells.
You can't get hig-performance mutability, but you can definitely implement a language with mutability on BEAM.
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.
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.gdcvault.com/play/1022186/Parallelizing-the-Naug...
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.
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.
Nice post, looks like a decent API. I wonder how similar each implementation of this model in Language X is and how easily it would be to test their implementation meet the spec.
I just didn’t want to derail the conversation away from D and the authors’ discovery.
Each variable can only be updated by a single and unique thread. If another thread (or actor) wants to modify it, it has to send a modification request messages to that one thread to do the job.
That's quite a difference!
Edit: Java threading model is fine for limited amounts of concurrency. Complexity escalates quickly with highly concurrent applications; the actor model makes reasoning about those programs far easier.
The main practical drawback, I would say, is that locks may suffer from the mutual exclusion problem; where you have contention, starvation and deadlocking. Also your ability to parallelize operations over shared state quickly vanishes as the number of threads grow...
If your system behavior does not immediately depend on the value of what you're changing, then actors are fantastic. You don't wait, it's async. You send the data to the controlling actor (which is an object in the OO sense itself). Your process continues on as if nothing changed until you need the updated results.
Process A
B ! {update, x, newVal},
%% A bunch of work
B ! {lookup, y, A}, % where y perhaps depended on x
Y = receive % Here we finally wait, if B is processing a lot of requests
{B,y,Val} -> Val
end,
%% more work
Process B
loop(State) ->
receive
{update, Var, Val} -> loop(update(State,Var,Val));
{lookup, Var, Pid} -> Pid ! lookup(State, Var), loop(State)
end.
Where B may be much more complex than that, in this case it looks like a simple KV store.The receive at the end of Process A is blocking and is the only time we end up with a lock in Process A. So if A never needs to do anything but send values to B, then A can run undisturbed by everyone else's data requests. This lends itself well to certain pipelined and dataflow architectures.