So, for example, network code gets better p99.
This, along with other sensible tradeoffs, is why Go ate a large slice of network software market.
Java's ZGC is multiple generations ahead, and I believe is a fairer tradeoff (overall lower throughput (due to read barriers over write barriers), but guaranteed <1ms pause time due to basically everything happening concurrently)
GC pauses are a necessity exactly for the part that can't be done in parallel, and Go is definitely not using ZGC's next-generation design that would decouple the pause's length from the heap size, so we still have our "arbitrary long pause" problem on the table.
>The Go GC avoids making the length of any global application pauses proportional to the size of the heap, and that the core tracing algorithm is performed while the application is actively executing.
>Brief stop-the-world pauses when the GC transitions between the mark and sweep phases.
I didn't benchmark it myself, though. Go is mostly used in stateless microservices where huge heap sizes are very uncommon. We usually scale horizontally, not vertically. Also, Go has value objects which can be allocated on the stack or embedded inside other objects, so it allocates far less garbage than Java. So practically for me Go's GC has been a non-issue, I barely remember it even exists.
Obviously there is not much to synchronize when there is only a single thread.
At least a couple of years ago Go had a very simplistic GC, but even today it is absolutely nowhere close to how good Java's GCs are (there is ZGC, for example, whose pause times are completely decoupled from heap size, so it can actually keep sub-millisecond pause times - the OS causes bigger pauses than that).
At most Go puts slightly less work on its GC due to having value types.
Sure, it's a "microbenchmark", but it might be eye-opening how big the difference is: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
(* in all the number crunching ones we have every other GC language, including Java and Go, confidently beat <3 )
Also, there might be some philosophical difference at play here, Java (the JVM) tends to expose only a very limited API (no structs, pointers, etc), but this allows more flexibility on the runtime part, while .NET let's the developer touch/control everything, but that might means less room for doing some advanced stuff on the runtime side.
The age-old generic-specific balance.
(Also, I like this playful competition between languages, much better than pointless flamewars :D)
If you use GOGC and GOMEMLIMIT to even the playing field (and note, use a Go program that isn't using sync.Pool) the difference in wall time is far less stark (though it's still there, maybe 5-15%; don't quote me, it's been a long time since I measured this and I don't remember exactly). (The difference in total CPU time is bigger.)
And finally, keep in mind this benchmark is hammering as hard as it can on the GC. How it impacts real applications depends on how much the application relies on the heap.
[1] https://go.dev/doc/gc-guide#Understanding_costs
[2] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Don't forget that the JVM has to allocate memory for all its subsystems as well, like the JIT compiler, so that 3x memory is not entirely heap usable by the program.
And I deliberately linked this benchmark, as the topic at hand is the GC itself.
> Don't forget that the JVM has to allocate memory for all its subsystems as well, like the JIT compiler, so that 3x memory is not entirely heap usable by the program.
That's fair. I recall doing my due diligence here and confirming it is actually using mostly heap memory, but again it's been a while and I could be wrong. (Also if the actual heap size is only ~100s of MiB and the rest of the subsystems need north of a GiB, that's much more than I would have anticipated.)
> And I deliberately linked this benchmark, as the topic at hand is the GC itself.
Sure. Not trying to suggest the benchmark doesn't have any utility, just that even for just GC performance, it doesn't paint a complete picture, especially if you're only looking at wall time.
No, I think you are right - I'm not trying to claim that most of that 1.7 GB is used by the core JVM, just that that's another factor (besides simply the different base heuristics on how much space to claim).
The only fair thing would probably be to re-run the benchmark multiple times with different available RAM (via cgroup) and see the graph. Though I'm fairly confident that Java would beat Go in this particular benchmark at any heap size.
? Go binary-trees programs shown together with Java programs here:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
(But stuff on those pages will be moving around for a few days.)
On the page you referenced:
— secs is elapsed seconds aka wall time aka wall clock
— mem is memory use
— gz is source code size
— cpu secs is cpu seconds
secs, mem, cpu secs as reported by BenchExec
Did it ever used to only show wall time? Or am I just completely misremembering?