(That said, C# and Java are no performance slouches and I expect they’d all be roughly comparable in the end if the Go code didn’t suck so much.)
Go isn't fast, it's decent but nothing special for AOT native code. (atleast the standard gc compiler, llgo, gccgo etc could end up being a different story). It can show good overall performance in I/O bound workloads because of native M:N green threading though. I imagine even that is no longer a point in Go's favor when Java's Virtual Threads land and become widespread however.
Go's only real interesting runtime feature was their emphasis on having a very low latency GC. This was somewhat special for a few years but between improvements made to .NET and Java's new GCs (Shanandoah and ZGC) that gap has been eliminated while these runtimes still provide better throughput on top of their now low latency.
There really isn't a good performance or resource usage reason to pick Go over C#/Java anymore, all of its advantages have been eroded.
Garbage collection accounted for over 95% of the run-time. It was a perfect pathological case where it continually thrashed below/above a GC threshold, without hitting the point where it would make more room. Adding just a couple kilobytes of ballast[1] pushed it over the limit to where it reliably took about 200ms. (and then I rewrote the test to stop allocating so much junk)
Go's GC is mostly pretty good in practice, but yeah. It's definitely not something you can rely on blindly.
Also I wish they'd stop claiming that its pause times are tiny - the stop-the-world pauses are indeed consistently very short, but it hardly matters when it can pause goroutines for far, far longer[2]. And e.g. when you're allocating, your goroutine may be paused to "help" the GC, which can take a substantial amount of time (as happened in that^ test), which frequently leads to very large tail-latency. Most people don't care about stop-the-world times, they care about tail-latency, and Go is just leaning on people's misunderstanding that one is the cause of the other. It can be, but it's not the only source.
[1]: https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i...
[2]: http://big-elephants.com/2018-09/unexpected-gc-pauses/ among other sources of individual goroutine pauses.
I think Go's GC is solidly "good enough", and much better than many languages, so I broadly like it. Coupled with built-in tracing that shows GC activity and it's in a pretty good position. It's more that all GCs have pathological behavior somewhere, and no amount of distracting hand-waving changes that.
All three use load-barriers which result in more work done in application threads as the pressure on the GC increases.
That said these GCs are substantially better than the Go GC so you are talking about much higher allocation throughput before running into these issues in practice vs Go.
At Google's size this means nothing. The protobuf and gRPC developers don't even always align well, let alone with an entire language ecosystem.
By the same token, C#'s excellent gRPC performance is the result of MS dumping a chunk of resources into gRPC/PB optimization most recently. For Google it's cheaper to buy 2x as many nodes as it is to make Go gRPC 2x faster.
That is definitely not true if Google was using Go gRPC at any reasonable scale.
A more accurate conclusion to draw from it is MSFT is using C# gRPC at scale and Google isn't using Go gRPC at scale. Which in itself makes a lot more sense because C# is the bread and butter language at MSFT for most large network services and Go isn't at Google, it barely has any penetration compared to Java and C++ (which predictably have much much faster gRPC and PB implementations).
When Java 19 lands you can have best of both worlds. Virtual Threads are equivalent to goroutines but you have access to better languages like Java, Kotlin and Clojure.
Valhalla has also produced an amount of language machinery that results in most of the same issues as Go. (E.g. "is this function receiving an interface or concrete value?" becomes "do I have a class, value class, or primitive class?") The performance ripples of requiring value objects to be immutable also remains to be seen.
I think by Java 21 they will be rock solid though which isn't really that far away in Java timescales.
Immutability is trivial to optimize away.