News for hackers. Stuff that matters.
Re:Baffled by generational garbage collection – wingolog by gf000 on Sunday February 09, 2025 @12:24PM (#42991999)
"Optimized" as in stops the user thread from doing useful work, when a lot of allocation happens?

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/...

Re:Baffled by generational garbage collection – wingolog by mknyszek on Sunday February 09, 2025 @04:02PM (#42993933)
Looking at only the CPU numbers from this benchmark is misleading. This site requires the use of default configurations for each language runtime, and JVMs tend to have a much larger default heap than the Go runtime. Tracing GCs tend to have a CPU/memory tradeoff built into them [1]. Compare the memory footprint of the best Go and best Java programs in terms of wall time [2] (the site doesn't make it easy, you have to go back and forth between the two links) and the difference is enormous (these Go programs are running with much smaller total heap sizes, so much less runway for the GC).

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/...

Re:Baffled by generational garbage collection – wingolog by gf000 on Sunday February 09, 2025 @06:24PM (#42995197)
I can see a ~3x difference in memory usage in case of JIT-compiled Java vs Go, but AOT-compiled Java comes in at less memory usage while still being faster than Go. I believe Java AOT uses the Serial GC, while the "normal" version defaults to G1, so there is that (the GC code is actually reused between Graal and OpenJDK, so we could remove this "variable")

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.

Re:Baffled by generational garbage collection – wingolog by mknyszek on Monday February 10, 2025 @01:13AM (#42997427)
Fascinating. I could see the Serial GC, if it's generational, just totally crush this particular benchmark. I wonder what the heap size heuristic is for the Serial GC.

> 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.

Re:Baffled by generational garbage collection – wingolog by gf000 on Monday February 10, 2025 @02:24AM (#42997730)
> 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.

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.

Re:Baffled by generational garbage collection – wingolog by igouy on Sunday February 09, 2025 @10:38PM (#42996675)
> [2] (the site doesn't make it easy, you have to go back and forth between the two links)

? 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.)

Re:Baffled by generational garbage collection – wingolog by mknyszek on Monday February 10, 2025 @12:58AM (#42997347)
I just meant that you cannot easily see wall time and memory use together on the same page. (I would love to be wrong about that.)
Re:Baffled by generational garbage collection – wingolog by igouy on Monday February 10, 2025 @12:21PM (#43002696)
Are you reading on your phone? Portrait orientation may lose columns.

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

Re:Baffled by generational garbage collection – wingolog by mknyszek on Monday February 10, 2025 @04:08PM (#43005178)
Yep! I was on my phone, sorry about that.

Did it ever used to only show wall time? Or am I just completely misremembering?

Re:Baffled by generational garbage collection – wingolog by igouy on Monday February 17, 2025 @02:36PM (#43082546)
It's been showing both for at-least 15 years (and before that claimed to be showing CPU secs, not Elapsed secs).
Re:Baffled by generational garbage collection – wingolog by neonsunset on Sunday February 09, 2025 @12:32PM (#42992049)
Binary-trees is very interesting. Showcases how Java can easily compete with native code that uses hand-managed arena allocators. While investigating why it is so much better than .NET* in this case I reached a conclusion that one of the major contributing factors is that it uses TLAB which "inlines" the GC allocation code completely into the callers, making the allocations indeed just thread-local pointer bumps. .NET has something similar (allocation context) but you do have to go through a call. I assume TLAB allocations and TLAB refills are just this much better regardless.

(* in all the number crunching ones we have every other GC language, including Java and Go, confidently beat <3 )

Re:Baffled by generational garbage collection – wingolog by gf000 on Sunday February 09, 2025 @12:49PM (#42992158)
Yeah, it's probably a bit unfair given that GC research itself is just Java.

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)