4 ms·
"Does this have Green threads?" is a common fallacy (along with mentioning function coloring) and arguably much worse API than Task/Future-based concurrency pri
by neonsunset 3y ago
"Does this have Green threads?" is a common fallacy (along with mentioning function coloring) and arguably much worse API than Task/Future-based concurrency primitives because it does not give idiomatic control over yielding/dispatching and hides an important characteristic of the executed code, specifically, calls being a promise to produce a result in the future (e.g. the example above with http requests is much clunkier without them).
Some languages intentionally opt for a much more limited way of doing it like Go with goroutines and channels, some have to retrofit an async-alternative to benefit the existing decades of code like Java, and some languages eventually got it right like F#, C#, in a way, Python and JS/TS, and, to dismay of many (and pain I sympathize with), Rust.
Also, in C#, the way to go about it is to just pick the abstraction you think fits the problem best, be it manual threading, async/await Tasks (or anything custom that integrates with it), channels or, as you mentioned, actors for which there are Akka.NET and Orleans. I believe F# is even more flexible in this area.
As for the implementation details of BEAM VM - just look at e.g. the times in 1BRC challenge for BEAM-based submissions - the rift between compiled languages and the former is immense and likely unclose-able, and this trend persists in any (micro)benchmark for BEAM I look at - the overhead of most trivial operations is just too damn high! It was a groundbreaking technology at its inception but today - likely not anymore.
Not sure what* makes you quick to assume that the people working on .NET, JVM and other platforms which care about performance in (massively) parallel domains don't know what they are doing but work-stealing schedulers are yesteryesterday news and "everyone" does them (.NET, Tokio, Go from what I know), both in the form of threadpool implementations and in the form of bespoke parallel abstractions (both examples in my previous comment have these). Same applies to latency-minimization techniques, priority scheduling (worst case you can always have a dedicated worker thread that runs at higher prio, or a scheduler with a pool of those) and more.
(* If you have been burned by Ruby - I did hear about it being subpar in all kinds of ways and observed being very slow but that should rather be an exception than the rule among other languages)
- pdimitar 3y ago> "Does this have Green threads?" is a common fallacy (along with mentioning function coloring) and arguably much worse API than Task/Future-based concurrency primitives... IMO both are really just handy abstractions. The salient question and point is: can we have 10,000+ perceptibly parallel tasks? The BEAM VM does it. And yes we're talking such that are CPU-intensive as well. > and some languages eventually got it right like F#, C#, in a way, Python and JS/TS If you say so. I haven't seen any proof of it for my 20+ years of programming. C# is quite the nice language but you are stretching the compliments towards it by ascribing to it that "it got parallelism right". It absolutely did not, as didn't 99% of all languages and runtimes out there. To this day. Having an API for good parallelism doesn't mean your runtime is prepared for it. That's why Orleans and Akka still cannot do what the BEAM VM can do, and likely never will. And Python and JS? You are just inserting a joke hoping I won't notice here, right? Right? I literally made dozens of thousands of bucks rewriting Python programs where people thought they were oh-so-clever with asyncio et. al., to Golang and to Rust. Easily 200x the throughput, and 99.9% of all parallel bugs disappeared overnight (well, after we launched, I mean). There were a grand total of 7 other bugs remaining we uncovered in the first month in production. I still keep contact with those old colleagues. The app ran unhindered for ~11 months before they finally hired a new team to keep developing it after they let go 10 out of 11 contractors after the project was mostly done (and I was in that group). > Also, in C#, the way to go about it is to just pick the abstraction you think fits the problem best Sure, do that, but in the end, and again, what matters most are the building blocks below -- do they enable the true lag-resistant parallelism that doesn't require a lot of fiddling? > this trend persists in any (micro)benchmark for BEAM I look at - the overhead of most trivial operations is just too damn high! It was a groundbreaking technology at its inception but today - likely not anymore. What's ground-breaking in terms of academic research matters not to industry, at least 90% of the time. I work with Elixir (and Golang, and Rust) professionally every day. Even today Phoenix is the most stable web stack I've ever encountered (and one of the very fastest throughout all dynamic languages and no, don't quote TechEmpower; they scarcely know what they're doing, though happily they became more open to PRs gradually which helped their later iterations a lot). Bombard the BEAM VM, DDoS it, all responses get slightly slower and slower as the load mounts up, but it has to be at its breaking point until you start seeing actually failing request-response pairs. Not to mention parts of your app's supervision tree can fail and they just get restarted without bringing the entire app down (like the database pool). Try doing that with Ruby. Or Java. Exception after exception, and the server OS process has to be restarted if somebody missed a catch clause (lol). In truth, Golang and Rust fare quite well too, but that's partly by the virtue of them being able to absorb much more hammering (they are between 20x to 1000x faster than Elixir depending on framework) and not strictly because of their runtimes. They do have good runtimes though. Not as fault-tolerant and lag-minimizing like the BEAM VM, but they are not far. > Not sure what makes you quick to assume that the people working on .NET, JVM and other platforms which care about performance in (massively) parallel domains don't know what they are doing 1. Sunk cost fallacy; 2. Stockholm syndrome; 3. Risk aversion; 4. Sinclair's law: "It Is Difficult to Get a Man to Understand Something When His Salary Depends Upon His Not Understanding It". You likely command a good salary with C# and have made a good career with it. Of course you will not want to think it's suboptimal. Shall I go on? And I am not saying that "they don't know what they are doing". I am saying there is too much inertia that nobody is ever going to make a revolutionary change -- everyone is too afraid and are just swallowing the problems and are becoming experts at avoiding them for as long as possible. Also financial stakeholders will never allow such revolutionary changes, but that's a very different topic. So yeah, not the same thing. > work-stealing schedulers are yesteryesterday news 1. I didn't suggest they are revolutionary. I suggested they are a good pattern that's proven, and you also noticed that. 2. What is "yesteryesterday's news" matters not. The ideas of the BEAM VM are quite old and they serve perfectly many businesses today. Are you suggesting trendiness > merit? I hope not. --- Overall, I will vehemently disagree that you just have to pick a language and it will all work out if you try hard enough. Absolutely not. This forced and imagined equality between languages and runtimes does NOT exist. Some of them are objectively better than others for jobs X and Y and I am tired of people pretending otherwise.
- neonsunset 3y agoI appreciate the long reply (no sarcasm) and the compliment regarding the salary haha (it makes for a nice aspirational goal, to have good total comp). I probably can't respond to the whole post but feel like we can come to an understanding. You mentioned 10_000 perceptibly parallel tasks so I threw together a small example - what if we had 10_000_000 concurrently executed tasks instead? This takes 1.2-1.5GB of RAM to run but it can give you a good showcase that .NET ThreadPool can take a lot of punishment and this is far from the worst you could see in one enterprise codebase or another: var tenMillionTasks = Enumerable .Range(0, 10_000_000) .Select(async i => { // Force the yield - this way the methods will continue the execution // in the form of scheduled continuations (work items), so that we pay // for context switching too, similar to a more realistic scenario. await Task.Yield(); for (var j = 10_000; j >= 0; j--) { // This is an okay proxy for doing some work since the // JIT is fairly conservative and won't auto-vectorize this // because its computation budget is prioritized on more impactful // optimizations like inlining, CSE, devirtualization, etc. // (this is completrly compensated by portable SIMD API) i++; } return i; }); // Scales linearly with the number of CPU cores var results = await Task.WhenAll(tenMillionTasks); var average = results.Average(); Console.WriteLine($"Done! The avg is {average}"); You can try it yourself if you're interested. To do so you can get an SDK from https://dot.net/download https://dot.net/download, execute 'dotnet new console', paste the code to Program.cs and then execute 'dotnet run -c Release'. This is more stressing to the runtime and is likely more fair than another comparison posts a year ago or so here on HN where that evaluated various runtimes by having the task simply wait for a period of time. Nonetheless, you can replicate that too by adding 'var delay = Task.Delay(TimeSpan.FromSeconds(5))' before tasks variable and replacing the lambda passed to .Select with 'async _ => await delay' to see how small the task overhead is.
- pdimitar 3y agoI appreciate you putting this together. :) If I am wrong, I'd be happy. It would be about time the big software stacks took proper parallelism to heart.