3 ms·
Can you say more about Go and homogeneous workloads? You might be referring to mixed IO- & CPU-bound tasks, and Go's former inability to preemptively schedule
by jeremyw 7y ago
Can you say more about Go and homogeneous workloads? You might be referring to mixed IO- & CPU-bound tasks, and Go's former inability to preemptively schedule tightly written loops? (Solved along the way: https://github.com/golang/go/issues/10958 https://github.com/golang/go/issues/10958)
Or something else?
- derefr 7y agoNah, just talking about the fact that Go doesn't scale well to using millions of channels to talk to millions of goroutines, each one executing as part of a small goroutine-cluster running independent functionality with its own requirements for data-architecture. Imagine you took all the AWS Lambda functions of all the different Lambda users, compiled them together into one binary, and then ran that single binary on one huge vertically-scaled server, allowing all the Lambda tenants to hit that server to run requests against their own workloads in the address-space of that single binary. That's a clear "heterogenous workload": one where you've spawned a bunch of green-threads within your process, and those green-threads are all trying to do different things. It's a million little job-shops under one roof (with, importantly, only one "door" in and out, that everyone has to coordinate to share); rather than one big factory where a million workers each have a defined job and there's a big loading bay that is declared from the top to be exclusively owned-and-operated by one department. Erlang handles the "million job-shops under one roof" use-case pretty well. Golang kinda-sorta handles it, but kinda falls over, too, because the Golang runtime (and the whole concept of reifying typed channels as objects) isn't tuned for the case where each task has spawned an ephemeral digraph of communication paths between ephemeral actors. Channels have a Golang-runtime-system overhead just for existing, a bit like processes or sockets do for the OS. So, like processes or sockets, you're not supposed to just create a channel for each little thing until you've got a million laying about; you're supposed to reuse or share them (and the goroutines they address) between multiple workloads. Idiomatic Golang code is written in a more dataflow-y pipeline-y SIMD-y way (the "factory with a million workers" above), where you've got static "actors" sending massive amounts of work between them, perhaps tagged with the request they're "about." When this is the 'data architecture' of your code, the Golang runtime can actually beat BEAM (using an equivalent architecyural abstraction, e.g. https://hexdocs.pm/flow/Flow.html https://hexdocs.pm/flow/Flow.html) in both throughput and latency, since it's exactly what the Golang runtime has been tuned for. The downside of the dataflow approach is that, compared to actors-as-little-Turing-machines, dataflow is a very hard data-architecture to reason about! (And also tends to mean that a logic error in any of your static dataflow worker-actors, causes a lot of "collateral damage" when it comes down, to the point that it often may as well bring down the whole system with it. A stall in an assembly line stops the whole factory; but a stall in a job-shop doesn't stop the whole economy!)