6 ms·
What can the Erlang / Golang runtimes do that the JVM can’t?
by raspasov 4mo ago
What can the Erlang / Golang runtimes do that the JVM can’t?
- pdimitar 4mo agoThousands of share-nothing actors (fibers / green-threads) with first-class support for communication between them, for a start. Erlang/Elixir -- immutability as well.
- rashkov 4mo agoWhat kind of software actually requires this? Honest question. Anything I can think of would probably be written by C++ devs
- pdimitar 4mo agoWeb / API services during bursts. Or just when you _really_ don't want to scale horizontally. Elixir / Golang can do this very well. And they do. I have supervised, led and authored such projects that are in production to this day. Rust too but it's lower-level and you kind of have to hand-roll OTP which of course will always fail.
- midnight_eclair 4mo agofrom experience, during bursts it's never actual web/api server that is bogged down, it's the downstream io bottlenecks. if your accepting layer is abstracted away and implemented correctly, there is very little performance difference between different concurrency approaches and all you're exposed to as developer is implementation of your handler functions.
- pdimitar 4mo ago(Upvoted for a really relevant and valuable refinement to the thread.) Admittedly that layer is almost always abstracted i.e. AWS / GCP and various other smaller hosted solutions that handle a good chunk of load balancing for us. In that landscape BEAM VM's strengths shine even brighter. I've seen firsthand that you can in fact bring a BEAM VM to its knees if you expose it just like that to the net. It's not pretty. Golang fares a touch better and Rust seems almost immune (provided one does not screw up their caching layer and don't do elementary N+1 query mistakes).
- zbentley 4mo agoNot the case; good abstractions are valuable, but the performance differences between runtimes are very real. Take the example of some simple HTTP<->blob store service gets slammed with millions of requests when someone using the API does a backfill via some framework on their end that aggressively scales request volume up and out. Something like, say, async Python/starlette with a coroutine per request is gonna perform slightly worse than Erlang, which in turn is gonna perform much worse than Go. You're right that those differences are sometimes marginal when the latency of whatever IO the backend's doing dominates the equation. However, in my experience huge volume surges show issues with the runtime (the thing managing/launching multiplexed request handler routines) or the ecosystem (the backend IO libraries' ability to work with the runtime's IO multiplexing and make things like request coalescing easy or automatic) more often than you'd think. It really takes surprisingly little volume to cripple a return-hello-world Phoenix app that indirects the "hello world" behind way too much middleware and message passing; it takes even less to kick over, say, a Gunicorn instance returning "hello world" at the bottom of the Django middleware stack. Golang with Gin, on the other hand, is surprisingly hard to cripple in the same way. And I say that as someone who likes Elixir and Python a lot more than I like Go!
- pdimitar 4mo agoThank you. As a guy who made a career out of Elixir (and begins to regret it recently but oh well) I agree that Elixir's throughput is not amazing. However, it can get very far and we should always optimize for the most common usages. I've personally rewritten one hobby and one professional projects from Elixir to Golang and loved the result; as you said, extremely difficult to bring down a Golang service to its knees. One clarification: Phoenix server behind Caddy/nginx fairs better btw. But, details. Your point stands. I am yet to see a Rust web/API service I wrote to _ever_ buckle under pressure and just crash. It was either an application bug (like the famous Cloudflare's `.unwrap()` error from the last weeks/months) or the Linux OOM killer. Literally never crashed. But I did witness it brutally murder a MySQL cluster because it couldn't serve it fast enough. That was both fun and terrifying to watch on the dashboards.
- zbentley 4mo ago
- RossBencina 4mo ago"requires" is of course subjective, there are always multiple ways to do something. But sometimes it is convenient to model a system as concurrent execution streams, for example: multiple sessions (servers), multiple entities (games, robotics), multiple in-flight transactions (any kind of i/o or concurrent compute). Agreed these are often C++ use-cases but there are obvious benefits to using Erlang or other virtual machines: memory safety, isolation, fault tolerance.
- lgrapenthin 4mo ago"As a rule of thumb, if your application never has 10,000 virtual threads or more, it is unlikely to benefit from virtual threads." https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html https://docs.oracle.com/en/java/javase/21/core/virtual-threa...
- pdimitar 4mo agoObviously. But it's really nice to have the option, and none of us knows the future. I've been bitten by those "0.1% chance" things much more times than I would be not-embarrassed to admit, and I know I a not alone.
- andersmurphy 4mo agoI believe the point they were making is you can have millions of virtual threads on the JVM no problem. Your information on the JVM is outdated.
- pdimitar 4mo agoGood, thanks for the grounding. I'll have to reevaluate at one point then. As I just posted in another comment (https://news.ycombinator.com/item?id=48384622 https://news.ycombinator.com/item?id=48384622), I'd probably drop Erlang/Elixir due to difficulties of employment and contracting -- if the more popular languages get those STM / share-nothing runtimes.
- whaleofatw2022 4mo agoBEAM threads are kinda magicsauce tho, instructions have a cost and after a certain cost total (quantums) the scheduler can divert to another virt thread to guarantee forward progress. Also the immutability rules etc make it easier to optimize this switching.
- zbentley 4mo agoEh, reduction counting isn't magic. Golang manages similar preemption semantics without counting that many operations (some tight loops do have barriers inserted every so often, but that's the exception and not the rule). And reduction counting has some serious costs! It slows the runtime down a shitload (and the BEAM is already in the bottom half of interpreted language runtimes by speed) and makes lots of JIT-flavored runtime optimizations slower or harder to implement. I like immutability too; I wish Java and Golang did more of it. It costs a lot in terms of unexpected copies in the BEAM though, there's less copy-elision optimization than you'd think. That especially bites if you're doing a ton of message passing, because of how process heaps are implemented and how garbage collection (traditional or ETS/ThreadProgress-based) works. I think what I want is something like Golang but with goroutine-based ownership semantics (or Rust with the Go runtime and goroutines): en excellent scheduler for extremely light-weight green threads, no refcounting or reduction counting, and all the clever optimizations around channel sending and copy elision--but no ability to use a value after it's sent to a channel, and only channel-based access to shared global state. That'd get most of the benefits of process-local heaps but without the (copying, cache/memory fragmentation) drawbacks.
- Barrin92 4mo ago>share-nothing actors although this is a deliberate choice rather than some accidental defect. Clojure went with STM as its concurrency model, if you're not buying into that and you want an Actor-centric language it's not the right choice to begin with.
- funcDropShadow 4mo agoSTM is seldom used in modern Clojure projects, it is certainly not the dominant model. Most projects I am aware of use a few or even exactly one atoms with immutable data structures.
- vips7L 4mo agoVirtual threads can do that too.
- deleted 4mo ago[deleted]
- Thaxll 4mo agoLow memory usage.