Yeah, if "letting developers define the memory layout of objects" means "value types" then that's obviously true.
But C# has value types, and the generational hypothesis certainly holds for C#. So while good memory management becomes less important, it doesn't change what the optimal MM approach is.
Of course it changes the MM approach if allocation rate goes down.
The higher the allocation rate, the larger the throughput advantage of compacting collectors.
But compacting collectors have all sorts of other costs - complexity, hard to make them concurrent, potential for high barrier overheads. So, if your allocation rate is low enough, then the throughput advantage of the compacting collector might be low enough that you might think to use some other kind of collector.
JSC is a great example. I should say that the allocation rate that matters is really the allocation rate scaled against the GC's speed; in classic terms we're talking about the cons/mark ratio. JS has a low cons/mark ratio (low relative allocation rate) because the JS collector is fundamentally no slower than any other one but the JS mutator is significantly slower than something like Java. So JS's allocation rate relative to the GC is lower than Java's allocation rate relative to the GC. This means that all GC throughput effects are dampened, which is one of the big reasons why JSC can get away with a collector that doesn't compact.
Also, the generational hypothesis has nothing to do with whether compaction is profitable. These are orthogonal things.
(EDIT: replaced "fundamentally now slower" with "fundamentally no slower", above)
"Also, the generational hypothesis has nothing to do with whether compaction is profitable. These are orthogonal things."
It is my understanding that these two are strongly linked, at least in practice.
The generational hypothesis states that most (even overwhelmingly most) allocations are unreachable very quickly.
So we employ moving/compacting collectors so that we no longer need to sweep all those dead allocations. These dead allocations are freed at no cost and we only pay for copying the live set.
Having written that, it occurs to me that the sentence I quote read oddly to me because I conflated 'compaction' with 'moving'.
Generational GC had nothing to do with moving objects. It’s possible to implement generational GC without moving anything. That’s how BDWGC, Edge’s GC, and JSC’s GC all work and there are probably others.
It’s true that those generational GCs that copy objects also sometimes use the address of the object to track the object’s generation. JSC’s GC uses a GC state byte in the object header to tell which generation an object is in.
> Generational GC had nothing to do with moving objects.
Eh, "nothing" is a strong word there. Technically you're right, but most generational GCs in practice use bump allocation in the nursery. The most common object copying occurs through minor collections, since compaction of the tenured generation is expensive and is done infrequently. So introducing generational GC in the usual way often does coincide with introducing copying.
Generational GC is a reusable concept. It’s very important for those who go to apply this concept to understand what you need to build a generational GC. My point is that you don’t need copying to do generational GC. The only thing that generational GC has to do with copying is that this is how the first generational GCs happened to be implemented and some people don’t know that there is another way, probably because people like you refuse to acknowledge that copying has nothing to do with generations.
You seem to be treating “generational GC” as a historical concept rather than an algorithmic concept. You’re right that from an historic perspective, generational GC and copying are related. But they are not technically or theoretically related.
> probably because people like you refuse to acknowledge that copying has nothing to do with generations.
Um, I'm well aware that generational GC doesn't require copying, and I mentioned this from the start. I don't even know what we're arguing about anymore.
pizlonator is correct. I didn't mean the layout of individual structs (that is its fields). What I meant is the layout of large numbers of objects in memory (relative to each other) which is what the article was benchmarking.
And it's not just that I don't need the GC to help me with that in a language that has value types. It's also that the GC might get it completely wrong if I'm not accessing the objects in creation order.
The generational hypothesis will hold in general, but I don't think it is very relevant when we're talking about processing large arrays filled with small objects. They're all going to be old gen.
> The generational hypothesis will hold in general, but I don't think it is very relevant when we're talking about processing large arrays filled with small objects. They're all going to be old gen.
Most apps aren't doing that. The generational hypothesis is one of the strongest empirical observations we've ever seen in computer science, and those are very hard to come by. Every attempt to deny the generational hypothesis that I've ever seen has been proven to be wrong in the end.
I think you're overstating the meaning of the empirical observation.
The observation as I understand it is that if you take the average over a large set of programs, then the hypothesis holds.
The observation is _not_ that if you take any program, then the hypothesis holds.
I'm hedging that it's the average over a large population of programs. I'm not making a claim about every program, only most programs, or rather - "most programs most of the time". I think that this is important because without a doubt, some programs are not generational at all.
Great example: go navigate from this page to another one. You want a full GC, not an eden GC, at this point. If your primary activity when browsing is navigating around and you're not spawning a new process for each navigation, then you are violating the generational hypothesis big time.
Note that this is somewhat orthogonal to the question of whether you should implement a generational GC. Generational GCs are great in part because they are rarely a regression on non-generational workloads.
Classic example: in the MMTk project they built this goofy "only for testing" non-generational collector that allocates in a bump nursery and then during GC it simultaneously evacuates the nursery and marks the "old" space. This non-generational GC, which has some traits of a generational GC, was slightly faster than their mark-sweep collector. It's often the case that generational GCs are really about a lot more than just exploiting the generational hypothesis.
Yes, there are some workloads that violate the generational hypothesis. But the vast majority of real-life programs adhere to it. I'm not arguing that the generational hypothesis holds over the entire universe of all possible programs; that would obviously be wrong. Rather I'm arguing against the implication expressed by some in this thread that generational GC isn't worth implementing because the generational hypothesis is wrong for most programs in X language.
I'm super puzzled why you think that the generational hypothesis should hold in every language, to the point that you're willing to reject empirical findings in other languages that contradict this presumption. If some language did not obey the generational hypothesis then this would not contract any single - not even one - prior empirical finding, unless there had also already been experiments on this subject in that particular language. The field of GC is way too young to start extrapolating findings from some languages to all languages.
I'm not rejecting empirical findings that contradict the generational hypothesis. I simply don't believe anybody has actually done any rigorous measurements of object mortality in Go and how it compares to other languages. In the absence of such measurements, I'm going to speculate similar allocation patterns to C#, which has a very similar model and the generational hypothesis certainly does hold for it.
I'd be more than happy to discard my speculation if it happens to be wrong!
Most apps aren't doing that, which is exactly why I said that the generational hypothesis will hold in general. I don't need convincing that optimizing for that case is generally a good idea. It just has very little to do with the point I was trying to make (apparently in a less than clear way)
The presence of struct types reduces allocation churn, so all memory management things become less important.