One limitation is that Go doesn’t have the concept of NUMA. Tbh, I know very little about this, I’m not sure how relevant is this or whether this is relevant at all.
Another limitation is garbage collection. Your Go program only have one centralized GC. When you scan for live object, it locks the whole process. Go GC is generational GC, which means it is quite smart. Object that lives long enough won’t be scanned as often. So, if you can limit the number of short-lived object, you may reduce the GC overhead. But, in order to know which object is old or young, you need to do some kind of bookkeeping everytime you assign to a pointer, which can add overhead to your program as well.
Channel can be a limiting factor too. Golang’s channel is a builtin language feature, so it’s hard to experiment with that. For example, let’s say you have a better channel implementation than Golang. Maybe your implementation is better in performance, but not as generic (maybe it only allows a single consumer). Well, you can’t change golang’s runtime. You can build your own multi-producer-single-consumer channel in golang manually using sync.Mutex
, and probably sync.Cond
, but then you can’t make your own mutex.
Or maybe, you have your own scheduler that’s better than golang’s scheduler. Maybe it’s better for your usecase. Well, you can’t implement it in Golang. The scheduler is already part of Go. I guess, if you really want to try, you can clone Go’s source code, modify the runtime code, and compile it yourself, but it’s just easier to use another language like C++.
So, I think for language like C, C++, Odin, Zig, and Rust, there are more things you can control. In Go, some things are already implemented in Go runtime and it’s hard to change it.