While the theoretical performance ceiling is higher, I’ve personally observed that it usually takes several times longer to write the systems in Rust/C++ and often the “naive” Rust implementation is less performant than the “naive” go implementation.
There are obviously cases where using Rust or C++ makes a lot of sense, but I prefer Go. I can get a performant prototype up and running quickly, the “critical path” can usually be micro optimized to within +/- 10% of Rust if you know what you’re doing, and by the time the Rust implementation matures the Go implementation has already had time to iterate repeatedly on key architectural, data structure, and algorithmic choices based on real world usage.
That’s just my 2cents though and doesn’t reflect the views of all the people I work with.
I've read a bit about algorithm-driven performance gains vs. hardware-driven performance gains... this seems like an argument for a third category: DX-driven performance gains.
The Go runtime is also finely tuned for exactly these kinds of workloads, with language level insight into blocking operations.
If we take the Tokio async runtime for Rust: it's not exactly the fastest runtime out there, comparatively new, you need to carefully avoid introducing blocking calls that stall the worker threads, there is no language level integration, and Rusts trait system currently often requires boxing of futures.
Combine all of that and you often end up with code that isn't noticeably faster than Go in practice.
One counterpoint though: I've found that Rust code takes longer to write, but it's significantly easier to refactor, thanks to the much stronger type system.
I think I know what you mean, but this isn't strictly true. In Go, the compiler decides for you, and you don't have to worry about it. The compiler allocates on the stack whenever it can (because it's faster and uses less memory), but if it determines that a variable "escapes the stack" or if it can't figure out whether it escapes or not, it allocates on the heap. You could have a simplistic (but correct) Go compiler which allocated everything on the heap -- interestingly, the Go spec doesn't mention the words "heap" or "stack" at all.
Go does give you a lot of control over allocations (stack or heap!) and memory layout, though, which makes it pretty good for relatively low-level performance work.
That is, unless you are forced to. One of the things about Go that is _very_ irritating is the way so many key APIs in the ecosystem make it almost impossible to re-use buffers. For example, protobuf and sarama generate throwaway memory at the same rate as message throughput.
jsonparser is great for JSON parsing: https://github.com/buger/jsonparser
fastcache is a great “zero gc” cache: https://pkg.go.dev/github.com/VictoriaMetrics/fastcache
Etc
Theoretically it can recover stack allocations with escape analysis though.
is there a typo: X can use tricks to avoid X ?
Furthermore, the compiler itself can help in the (re)writing of code to effect stack allocations of variables, avoiding runtime reliance on the garbage collector.
This is effected, in general, through "escape analyses" that prevent reliance, at runtime, on garbage collection.
Punchline is calling Go a garbage collected language can be entirely inaccurate, depending on the program being compiled.
Language wise, Rust might be a good choice as some projects like tikv already use it in a pretty larger scale systems. But I'd also like to know both pros/cons of these languages.