A compiler that does precise root-finding will typically output a side-table indicating which slots in a stack frame hold references to heap objects. These lifetimes aren’t always precise, in the sense that although they precisely enumerate heap references, those heap references might actually not be used in the continuation of the stack frame. When GC occurs, it might mark more objects as live than are actually live, which is the imputed disadvantage of conservative collectors.
This is not necessarily accurate with true precise tracking E.g.:
using System.Runtime.CompilerServices;
Example();
// Skip Tier 0 compilation which does not track gcrefs as precisely
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
static void Example() {
var obj = new object();
var wr = new WeakReference(obj);
for (var i = 0; i < 3; i++) {
Console.WriteLine(obj);
}
GC.Collect();
Console.WriteLine(wr.IsAlive);
}
This works in much more advanced scenarios too.
Unfortunately, I can't link a simple document that covers this in detail from the top of my head but there's a wealth of information in Konrad Kokosa's works:
In .NET, even in optimized methods, there can be "untracked" lifetimes where a stack slot is reported live to GC throughout the extent of a method, so presumably these can lead to the "over-reporting" cases mentioned.
The number of trackable lifetimes was 64 in .NET Framework but has been steadily increased in modern .NET and is now 1024, so it's rarely a capacity issue; but there are cases where we can't effectively reason about lifetimes.
For us another big drawback to conservative scanning is that any object referred to by a conservative reference cannot be relocated, since the reference might be live and is not guaranteed to be a GC reference; these objects are (in our parlance) effectively pinned, and this causes additional overhead.
Comments
This is not necessarily accurate with true precise tracking E.g.:
This works in much more advanced scenarios too.Unfortunately, I can't link a simple document that covers this in detail from the top of my head but there's a wealth of information in Konrad Kokosa's works:
.NET GC internals lectures: https://www.youtube.com/watch?v=8i1Nv7wGsjk&list=PLpUkQYy-K8...
Pro .NET Memory Management (which is a great book in general): https://prodotnetmemory.com/
In .NET, even in optimized methods, there can be "untracked" lifetimes where a stack slot is reported live to GC throughout the extent of a method, so presumably these can lead to the "over-reporting" cases mentioned.
The number of trackable lifetimes was 64 in .NET Framework but has been steadily increased in modern .NET and is now 1024, so it's rarely a capacity issue; but there are cases where we can't effectively reason about lifetimes.
For us another big drawback to conservative scanning is that any object referred to by a conservative reference cannot be relocated, since the reference might be live and is not guaranteed to be a GC reference; these objects are (in our parlance) effectively pinned, and this causes additional overhead.
Thanks!
I knew about 1000 (turns out 1024) limit for method locals, in hindsight it does make sense for it to apply to gcref tracking just as much...