An on-topic, comment critical of Go would be: it's silly that cgo is considered so bad (why?) that people go to such great lengths to avoid it, when so many (otherwise great) Wasm runtimes already exist.
But if I'm already using Go and need a Wasm runtime, my options are wazero or wasmtime-go (etc), not LISP and C#.
Also, CGo design is a trade-off, and makes perfect sense. Nobody has come up with a design to have both fast C ABI FFI (no stack/thread switch) and cheap green threads (small stacks). People complaining about CGo rarely seem to understand this.
I regularly look sad at profiles of production cgo programs where findRunnable is in the top 10.
But cgo being slow (and above all displeasent to work with) is what I'd consider valid criticism of Go.
Go has other advantages, FFI is not one of them.
A quick search reveals cgo is mainly slow due to overhead when invoking non-pure go functions (i.e. C-functions).
https://stackoverflow.com/questions/28272285/why-cgos-perfor...
How does this relate and interact with goroutines in a performance impacting way? As in, why is the case with goroutines any different?
Edit: @neonsunset: Thanks <3. The thread you linked covers erl/BEAM and C#/dotnet, and makes salient points of skepticism about the practical need for millions of routines. I'm still curious what makes the Go story worse or different?
Cgo calls take a lot of resources, or, at least, take a lot of resources if you're trying to do a million of them simultaneously. (In absolute terms, Cgo slowdown is one of those things that programmers hear bandied about in conversations and can easily come away with the impression that a single Cgo call will consume 150 milliseconds or something and instantly bring your program to a crawl, but a single call is not noticeable. It's only really a problem when scaled somehow. See also the belief that GC guarantees that my program is going to be frozen for 250 milliseconds every couple of seconds.) In this context probably the most relevant one is they need a full OS thread to work in the C runtime, so a million simultaneous Cgo calls would require a million simultaneous OS threads, and that's still a pretty big expense today.
For obvious reasons, Go can only "handle" millions of goroutines if all but some approximation of the number of CPUs available are doing nothing at any given time. The only common type of program that might have this circumstance is really network servers.
Calling out to ffi requires preparing both the stack and the runtime for that call. It’s theoretically possible to avoid the need for this preparation, but with some caveats - you have to block the gc view of parts of the world (the parts ffi could be using) and you need to ensure that your runtime isn’t interacting badly with the ffi environment (e.g. not sending it signals and so on). Go’s arrangement (as is common with most runtimes) needs to active work to prevent both problems, and the cost of cgo is largely the cost of doing those things. Most managed runtime/gc languages have these challenges and it’s one of the axes that people say makes them not systems languages (a poorly defined term for sure, but fair on an axis of “can’t make calls to other software modules at base call cost”)
C# trivially pins only select buffers or other data that need to be observed by whatever it is being called across FFI while GC is free to move and handle everything else (or, should the data need to be marshalled, it can be stack-allocated and pointer to such is passed instead, or it can be malloc and freed just like you can do in C). Years of work in this area made the impact of FFI as minimal as possible, with extra levers available to the user to completely erase the overhead (suppressing GC frame transition, using methods to allocate objects on the dedicated pinned heap, static linking for aot binaries, etc.).
Go's GC implementation is comparatively simple and has other inefficiencies such as very expensive write barriers.
*sometimes the tradeoff is just a product, which Go is, that was cheaper to produce, despite common beliefs
(C# can handle millions of active tasks, and so can Elixir, though with greater overhead as it offers a different set of tradeoffs)
That is how we end up using less capable alternatives.