> Modern garbage collection assumes that the weak generational hypothesis holds and that most objects die young
Aside, I'm curious how first-class support for value types and Go-style stack allocation via escape analysis changes the value proposition of the generational hypothesis. If we hypothesize that short-lived objects are local to a single function scope (and thus eligible for stack allocation either explicitly via a value type or heuristically via escape analysis) then it might completely upend the generational hypothesis and make it so that relatively more long-lived objects are getting heap-allocated. Surely someone's done some studies on this?
> it might completely upend the generational hypothesis
The generational hypothesis is about object lifetime, and that doesn't change.
It does change the relevance of the generational hypothesis to garbage collection.
> Surely someone's done some studies on this?
The go team has, and that's why go doesn't have a generational GC. The complexity of adding generational support, especially in a mutation-based language (so needing memory barriers and the like) was found not to benefit when a significant fraction of the newborn objects don't even reach the youngen. See https://github.com/golang/go/discussions/70257#discussioncom... from the current discussion of adding opt-in ad-hoc support for memory regions.
I'm open to believing that this is true, but some real numbers would be nice. Surely it wouldn't be a hugely invasive change to fork the Go compiler, change the stack allocation check to `return false`, and then measure the overhead of the garbage collector on real Go programs with stack allocation both enabled and disabled.
The reason escape analysis is not "good enough" is why we have project Valhalla trying to bring Value Types into the JVM.
I don't have numbers at hand, but I remember the JDK Expert Group talking about this extensively in the past and why they deferred bringing Value Types for such a long time. They hoped complex enough EA can get rid of indirections and heap allocations but it just wasn't powerful enough, even with all advances throughout the years.
Yeah in Java land specifically I think the question would become, "does the generational hypothesis still hold up once we have Valhalla and a much larger share of short-lived objects are stack allocated as value types?" but of course it may be years until the ecosystem reaches that point, if ever.
As shown by C#, it will generally continue to be relevant since both primarily use JIT compilation with ability to modify code at runtime which can violate inter-procedural escape analysis assumptions leading to heap allocations of the objects that are passed down to the callees (there is work scheduled for .NET 10 to address this, at least for AOT compilation where interproc analysis conclusions will be impossible to violate).
You can craft a workload which violates the hypothesis by only allocating objects that live for a long time but both JVM and .NET GC implementations are still much faster designs than Go's GC which prioritizes small memory footprint and consistent latency on low allocation traffic (though as of .NET 9, SRV GC puts much more priority on this, making similar tradeoffs).
Go has much more significant stack allocation capabilities, most notably it has no problem allocating entire structs on the stack so doesn't need scalar replacement, which falls over if you breathe on it (https://pkolaczk.github.io/overhead-of-optional/).
> the weak generational hypothesis does not hold up well with respect to heap-allocated memory in many real-world Go programs (think 60-70% young object mortality vs. the 95% typically expected)
Well, variables cannot be forced to stack specifically. They are placed in the "local scope". And that would usually be either stack or CPU registers - thinking in stack only is a somewhat flawed mental model.
Both C# and F# complicate this by supporting closures, iterator and async methods which capture variables placing them in a state machine box / display class instead which would be located on the heap, unless stack-allocated by escape analysis (unlikely because these usually cross method boundaries).
However, .NET has `ref structs` (or, in F#, [<Struct; IsByRefLike>] types) which are subject to lifetime analysis and can never be placed on the heap directly or otherwise.
Historically, Hotspot's escape analysis only resulted in avoided heap allocations (via scalar replacement) if all uses were inlined. I don't think this has changed.
But stack allocated objects are not part of the heap and therefore not even part of Garbage Collection? And afaik stack allocation is already done for objects which don't escape a method.
What a time to be alive, I read it the opening fully expecting to see an open source automated trashcan that takes itself to the curb each Monday. I was disappointed to find out it is about an actual garbage collection algorithm.
> Modern garbage collection assumes that the weak generational hypothesis holds and that most objects die young
Aside, I'm curious how first-class support for value types and Go-style stack allocation via escape analysis changes the value proposition of the generational hypothesis. If we hypothesize that short-lived objects are local to a single function scope (and thus eligible for stack allocation either explicitly via a value type or heuristically via escape analysis) then it might completely upend the generational hypothesis and make it so that relatively more long-lived objects are getting heap-allocated. Surely someone's done some studies on this?
You might be interested in this talk: https://go.dev/blog/ismmkeynote
> it might completely upend the generational hypothesis
The generational hypothesis is about object lifetime, and that doesn't change.
It does change the relevance of the generational hypothesis to garbage collection.
> Surely someone's done some studies on this?
The go team has, and that's why go doesn't have a generational GC. The complexity of adding generational support, especially in a mutation-based language (so needing memory barriers and the like) was found not to benefit when a significant fraction of the newborn objects don't even reach the youngen. See https://github.com/golang/go/discussions/70257#discussioncom... from the current discussion of adding opt-in ad-hoc support for memory regions.
AFAIK not nearly enough stuff gets caught by escape analysis - and thus stack allocated - to make a difference.
I'm open to believing that this is true, but some real numbers would be nice. Surely it wouldn't be a hugely invasive change to fork the Go compiler, change the stack allocation check to `return false`, and then measure the overhead of the garbage collector on real Go programs with stack allocation both enabled and disabled.
The reason escape analysis is not "good enough" is why we have project Valhalla trying to bring Value Types into the JVM.
I don't have numbers at hand, but I remember the JDK Expert Group talking about this extensively in the past and why they deferred bringing Value Types for such a long time. They hoped complex enough EA can get rid of indirections and heap allocations but it just wasn't powerful enough, even with all advances throughout the years.
I may have been answering past you - I am thinking of Java running on the JDK here. And indeed I may be out of date also.
Yeah in Java land specifically I think the question would become, "does the generational hypothesis still hold up once we have Valhalla and a much larger share of short-lived objects are stack allocated as value types?" but of course it may be years until the ecosystem reaches that point, if ever.
As shown by C#, it will generally continue to be relevant since both primarily use JIT compilation with ability to modify code at runtime which can violate inter-procedural escape analysis assumptions leading to heap allocations of the objects that are passed down to the callees (there is work scheduled for .NET 10 to address this, at least for AOT compilation where interproc analysis conclusions will be impossible to violate).
You can craft a workload which violates the hypothesis by only allocating objects that live for a long time but both JVM and .NET GC implementations are still much faster designs than Go's GC which prioritizes small memory footprint and consistent latency on low allocation traffic (though as of .NET 9, SRV GC puts much more priority on this, making similar tradeoffs).
Go has much more significant stack allocation capabilities, most notably it has no problem allocating entire structs on the stack so doesn't need scalar replacement, which falls over if you breathe on it (https://pkolaczk.github.io/overhead-of-optional/).
According to https://github.com/golang/go/discussions/70257#discussioncom...
> the weak generational hypothesis does not hold up well with respect to heap-allocated memory in many real-world Go programs (think 60-70% young object mortality vs. the 95% typically expected)
Is there a language that makes this explicit, allocates the variables on the stack via compiler enforced notation?
C, C++, Rust, Zig, …
C# (.NET in general) :)
Well, variables cannot be forced to stack specifically. They are placed in the "local scope". And that would usually be either stack or CPU registers - thinking in stack only is a somewhat flawed mental model.
Both C# and F# complicate this by supporting closures, iterator and async methods which capture variables placing them in a state machine box / display class instead which would be located on the heap, unless stack-allocated by escape analysis (unlikely because these usually cross method boundaries).
However, .NET has `ref structs` (or, in F#, [<Struct; IsByRefLike>] types) which are subject to lifetime analysis and can never be placed on the heap directly or otherwise.
Historically, Hotspot's escape analysis only resulted in avoided heap allocations (via scalar replacement) if all uses were inlined. I don't think this has changed.
But stack allocated objects are not part of the heap and therefore not even part of Garbage Collection? And afaik stack allocation is already done for objects which don't escape a method.
How does this relate to Shenandoah's region selection logic? Doesn't it have similar behavior?
What a time to be alive, I read it the opening fully expecting to see an open source automated trashcan that takes itself to the curb each Monday. I was disappointed to find out it is about an actual garbage collection algorithm.
I know this is completely off-topic, but you might be interested in this[1] YouTuber who did something not far off that...
1: https://www.youtube.com/watch?v=VhYEOG9LOIk