News for hackers. Stuff that matters.

mknyszek

Re:Generic Methods in Go 1.27 on Thursday August 20, 2026 @10:44PM (#49383087)
There's a little more nuance to this. The compiler knows the static type of any value placed into an interface value, and emits code to place a concrete type pointer (or itab pointer) into the interface value. You can see this in the disassembly.

Later when you pass this value around and assign it to places, sure, there may be a different type depending on where the interface value came from. But the construction of these values is very much static.

In this sense I wouldn't really call this "resolv[ing] while the program runs" unless you take a very broad sense of "resolution."

I think this is a misunderstanding. If the program out-paces the GC because the GC guessed the trigger point wrong, something has to give.

In Go, what gives is goroutines have to use some of their time slice to assist the GC and pay down their allocations.

In Java, I believe what you used to get was called "concurrent mode failure" which was somewhat notorious, since it would just stop the world to complete the mark phase. I don't know how this has changed. Poking around a little bit it seems like something similar in ZGC is called "allocation failure"?

The GC assist approach adopted by Go was inspired by real-time GC techniques from the literature and in practice it works nicely. It's not perfect of course, but it's worked just fine for lots of programs. From a purely philosophical point of view, I think it results in a more graceful degradation under unexpectedly high allocation pressure than stopping the world, but what happens in practice is much more situational and relies on good heuristics in the implementation.

Re:Understanding the Go Runtime: The Scheduler on Friday March 13, 2026 @10:33AM (#47365029)
> Thus, they’ll probably never fix it.

I'm sorry you had a bad experience with Go. What makes you say this? Have you filed an issue upstream yet? If not, I encourage you to do so. I can't promise it'll be fixed or delved into immediately, but filing detailed feedback like this is really helpful for prioritizing work.

Re:Understanding the Go Runtime: The Scheduler on Friday March 13, 2026 @10:21AM (#47364861)
The compiler generates code to spill arguments to the stack at synchronous preemption points (function entry). Signal-based preemption has a spill path that saves the full ABI register set.
Re:Baffled by generational garbage collection – wingolog 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 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 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 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:Major standard library changes in Go 1.20 on Wednesday January 18, 2023 @01:56PM (#34431200)
> At no point is it invalidated, and the new arena will now start allocating new stuff from the start of the chunk. Right? And my old pointer still works, and the data inside it is at some point overwritten.

It doesn't allocate at the start of the chunk, it just picks up wherever the last one left off. That allocated memory in the chunk from previous arena allocations is not reused until the chunk as a whole is unmapped and the GC can confirm that no more pointers point into that chunk's address space (if you leave a dangling pointer, you only waste address space). This points-into property is cheap to check, it's equivalent to whether the chunk has been marked by the GC.

Again, I think that MSAN/ASAN should probably just be more strict with these kinds of use-after-frees. You won't crash, but it's still technically incorrect. (Not much can be done about the "GC is in the mark phase" case, unfortunately. Otherwise MSAN/ASAN will complain when the GC inevitably tries to access a pointer into a delayed chunk.)

Re:Major standard library changes in Go 1.20 on Tuesday January 17, 2023 @05:39PM (#34420510)
The functionality to poison the address space is there, and it does work.

What you're encountering is one of two exceptions in the implementation where you might not get an immediate failure:

1. If you use only a very small amount of an arena chunk and free it, it goes back on a reuse list as an optimization. Accessing that chunk's memory, despite the fact that the arena was freed, is entirely memory safe: nothing else will use that memory. That address space will be properly poisoned once the arena chunk is full, or close to it. 2. If the GC is actively marking, arena chunk poisoning is delayed to avoid races with the GC that might cause it to dereference a pointer into poisoned memory. The arena chunk is poisoned as soon as the GC is done.

The API explicitly does not guarantee a crash on use-after-free[1] because the Go team wanted a valid implementation of arenas to be simply "new" for New and noop for Free. The point is to just stay memory safe and have a high probability of catching an issue _in production_ where presumably arenas are filled up (otherwise why are you using arenas?).

[1]: https://cs.opensource.google/go/go/+/master:src/arena/arena....

Re:JVM Anatomy Quarks on Friday January 31, 2020 @04:36PM (#22206053)
> He is also the maintainer of JMH, Java Microbenchmarking Harness, a tool I miss in about every other language especially Go. Because writing microbenchmarks is really difficult especially on a JIT that wants to rewritw and optimize things behind your back a couple times. Hotspot can be both brilliant and maddening.

I'm assuming you're aware of the microbenchmarking framework built into Go's testing framework? If so, can you elaborate on where it falls short? I have my own gripes, but it would be nice to understand where others are coming from on this.

> In high performance Java you often rewrite a lot of the base libraries in a very different style that gives you tight control over escape analysis, GC, call site inlining, etc. You actually have a decent amount of control for such a high-level language.

In case you're not aware, the standard Go toolchain does expose some of the optimizations the compiler does, notably escape analysis and bounds check elimination. In the latest release you'll have the option to even export this information as JSON to inspect it programmatically.

Other things, like inlining, are not there (AFAIK), but that's partially because the toolchain is still maturing. For example mid-stack inlining (i.e. inlining any calls to non-leaf functions) is actually a relatively recent addition. Go will also let you choose to not inline something. There's still a lot of work to be done around inlining in general, and visibility into the process would be a nice improvement.

I'd also like to point out that part of the reason the GC has so few knobs has to do with maintainability. Every new knob means expanding the space of configurations significantly, and making sure they all continue to work is a big task. For example, V8 exposes a lot of knobs, but (IIUC) aside from a few default configurations shipped in Chrome, any deviation from those and you're considered "on your own", mainly because of this maintainability problem.

With that being said, I'm not really sure how OpenJDK deals with this issue; maybe there's just enough people out there and enough resources behind the project that it's fine?

> In Go, I haven't been able to find that control. The Go team seems to have taken an opposite approach and removed your control (I often joke about Go just being short for "Go Fuck Yourself" because of its attitude against developer control and the teams's "if we don't need it you don't need it" attitude).

I don't think this is the intended messaging from the Go team, and there's been efforts on their part to shed this image. I think part of it is maturity of the toolchain; Java has nearly 15 years on Go and in some cases there honestly isn't all that much to give visibility into or control over yet. Another part of it is an overall conservative approach toward evolution of the language and of APIs, primarily for long-term maintainability and compatibility. Expanding the API (including performance knobs) usually needs to show a clear net win (see SetMaxHeap, which gives you more control but never really made it in; it still exists as a patch).

> It is resources like this that really make Java shine in its pro high performance developer attitude. (Current Go issue, getting select and channels to operate anywhere remotely efficiently and trying to find a way to keep high CPU goroutines on different OS threads - so far not much luck).

You should definitely file a bug if you have the bandwidth to do so and haven't already. Channels and scheduling should be efficient and smart by default, and finding situations where they make poor decisions is how the runtime improves. The team is fairly responsive to such bugs and even if they don't get resolved immediately, having it on their radar will only help the team make better design decisions going forward.